From 8d5f678b2580a458d9ce38f200717304be4f23f9 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Tue, 5 May 2026 15:01:01 -0700 Subject: [PATCH] Initial Prey Game integration support. --- neo/Doom3.sln | 61 +- neo/MayaImport.vcxproj | 28 + neo/cm/PreyCM/CollisionModel.h | 146 + neo/cm/PreyCM/CollisionModel_contacts.cpp | 48 + neo/cm/PreyCM/CollisionModel_contents.cpp | 614 ++ neo/cm/PreyCM/CollisionModel_debug.cpp | 498 + neo/cm/PreyCM/CollisionModel_files.cpp | 598 ++ neo/cm/PreyCM/CollisionModel_load.cpp | 3955 ++++++++ neo/cm/PreyCM/CollisionModel_local.h | 527 ++ neo/cm/PreyCM/CollisionModel_rotate.cpp | 1672 ++++ neo/cm/PreyCM/CollisionModel_trace.cpp | 229 + neo/cm/PreyCM/CollisionModel_translate.cpp | 1100 +++ neo/curllib.vcxproj | 24 + neo/doomdll.vcxproj | 238 +- neo/doomdll.vcxproj.filters | 38 + neo/doomdll.vcxproj.user | 8 + neo/framework/BuildDefines.h | 5 + neo/framework/Common.cpp | 152 +- neo/framework/Common.h | 8 + neo/framework/DeclFX.cpp | 30 + neo/framework/DeclFX.h | 18 +- neo/framework/DeclManager.h | 3 + neo/framework/DeclSkin.h | 2 +- neo/framework/DeclTable.h | 2 +- neo/framework/KeyInput.cpp | 33 + neo/framework/KeyInput.h | 4 + neo/framework/Licensee.h | 19 +- neo/framework/Session.cpp | 2 + neo/framework/Session_menu.cpp | 2 + neo/framework/UsercmdGen.h | 6 + neo/framework/async/AsyncClient.cpp | 4 + neo/framework/async/AsyncServer.cpp | 8 + neo/game.vcxproj | 34 + neo/idlib.vcxproj | 26 +- neo/idlib/BitMsg.h | 44 + neo/idlib/containers/List.h | 2 +- neo/idlib/math/Vector.cpp | 36 + neo/idlib/math/Vector.h | 30 + neo/idlib/precompiled.h | 13 +- neo/opengl/opengl.vcxproj | 98 + neo/prey/MayaImport/Maya4.5/maya.h | 47 + neo/prey/MayaImport/Maya6.0/maya.h | 47 + neo/prey/MayaImport/exporter.h | 434 + neo/prey/MayaImport/maya5.0/maya.h | 47 + neo/prey/MayaImport/maya_main.cpp | 3152 +++++++ neo/prey/MayaImport/maya_main.h | 18 + neo/prey/MayaImport/mayaimport.def | 4 + neo/prey/Prey/ai_Navigator.cpp | 268 + neo/prey/Prey/ai_Navigator.h | 41 + neo/prey/Prey/ai_centurion.cpp | 529 ++ neo/prey/Prey/ai_centurion.h | 84 + neo/prey/Prey/ai_crawler.cpp | 115 + neo/prey/Prey/ai_crawler.h | 24 + neo/prey/Prey/ai_creaturex.cpp | 1669 ++++ neo/prey/Prey/ai_creaturex.h | 139 + neo/prey/Prey/ai_droid.cpp | 660 ++ neo/prey/Prey/ai_droid.h | 73 + neo/prey/Prey/ai_gasbag_simple.cpp | 801 ++ neo/prey/Prey/ai_gasbag_simple.h | 90 + neo/prey/Prey/ai_harvester_simple.cpp | 838 ++ neo/prey/Prey/ai_harvester_simple.h | 79 + neo/prey/Prey/ai_hunter_simple.cpp | 2141 +++++ neo/prey/Prey/ai_hunter_simple.h | 128 + neo/prey/Prey/ai_inspector.cpp | 136 + neo/prey/Prey/ai_inspector.h | 28 + neo/prey/Prey/ai_jetpack_harvester_simple.cpp | 662 ++ neo/prey/Prey/ai_jetpack_harvester_simple.h | 66 + neo/prey/Prey/ai_keeper_simple.cpp | 694 ++ neo/prey/Prey/ai_keeper_simple.h | 98 + neo/prey/Prey/ai_mutate.cpp | 63 + neo/prey/Prey/ai_mutate.h | 15 + neo/prey/Prey/ai_mutilatedhuman.cpp | 390 + neo/prey/Prey/ai_mutilatedhuman.h | 23 + neo/prey/Prey/ai_passageway.cpp | 144 + neo/prey/Prey/ai_passageway.h | 49 + neo/prey/Prey/ai_possessedTommy.cpp | 120 + neo/prey/Prey/ai_possessedTommy.h | 30 + neo/prey/Prey/ai_reaction.cpp | 639 ++ neo/prey/Prey/ai_reaction.h | 261 + neo/prey/Prey/ai_spawncase.cpp | 227 + neo/prey/Prey/ai_spawncase.h | 34 + neo/prey/Prey/ai_speech.cpp | 403 + neo/prey/Prey/ai_speech.h | 102 + neo/prey/Prey/ai_sphereboss.cpp | 533 ++ neo/prey/Prey/ai_sphereboss.h | 45 + neo/prey/Prey/anim_baseanim.cpp | 104 + neo/prey/Prey/anim_baseanim.h | 29 + neo/prey/Prey/force_converge.cpp | 173 + neo/prey/Prey/force_converge.h | 44 + neo/prey/Prey/game_afs.cpp | 100 + neo/prey/Prey/game_afs.h | 49 + neo/prey/Prey/game_alarm.cpp | 76 + neo/prey/Prey/game_alarm.h | 21 + neo/prey/Prey/game_anim.cpp | 244 + neo/prey/Prey/game_anim.h | 23 + neo/prey/Prey/game_animBlend.cpp | 276 + neo/prey/Prey/game_animBlend.h | 6 + neo/prey/Prey/game_animDriven.cpp | 287 + neo/prey/Prey/game_animDriven.h | 40 + neo/prey/Prey/game_animatedentity.cpp | 424 + neo/prey/Prey/game_animatedentity.h | 74 + neo/prey/Prey/game_animatedgui.cpp | 208 + neo/prey/Prey/game_animatedgui.h | 34 + neo/prey/Prey/game_animator.cpp | 251 + neo/prey/Prey/game_animator.h | 32 + neo/prey/Prey/game_arcadegame.cpp | 766 ++ neo/prey/Prey/game_arcadegame.h | 188 + neo/prey/Prey/game_barrel.cpp | 181 + neo/prey/Prey/game_barrel.h | 30 + neo/prey/Prey/game_bindController.cpp | 290 + neo/prey/Prey/game_bindController.h | 68 + neo/prey/Prey/game_blackjack.cpp | 508 + neo/prey/Prey/game_blackjack.h | 70 + neo/prey/Prey/game_cards.cpp | 130 + neo/prey/Prey/game_cards.h | 63 + neo/prey/Prey/game_cilia.cpp | 300 + neo/prey/Prey/game_cilia.h | 36 + neo/prey/Prey/game_console.cpp | 822 ++ neo/prey/Prey/game_console.h | 148 + neo/prey/Prey/game_damagetester.cpp | 180 + neo/prey/Prey/game_damagetester.h | 39 + neo/prey/Prey/game_dda.cpp | 681 ++ neo/prey/Prey/game_dda.h | 128 + neo/prey/Prey/game_deathwraith.cpp | 559 ++ neo/prey/Prey/game_deathwraith.h | 48 + neo/prey/Prey/game_debrisspawner.cpp | 445 + neo/prey/Prey/game_debrisspawner.h | 47 + neo/prey/Prey/game_dock.cpp | 70 + neo/prey/Prey/game_dock.h | 49 + neo/prey/Prey/game_dockedgun.cpp | 16 + neo/prey/Prey/game_dockedgun.h | 19 + neo/prey/Prey/game_door.cpp | 586 ++ neo/prey/Prey/game_door.h | 69 + neo/prey/Prey/game_eggspawner.cpp | 281 + neo/prey/Prey/game_eggspawner.h | 53 + neo/prey/Prey/game_energynode.cpp | 126 + neo/prey/Prey/game_energynode.h | 35 + neo/prey/Prey/game_entityfx.cpp | 637 ++ neo/prey/Prey/game_entityfx.h | 62 + neo/prey/Prey/game_entityspawner.cpp | 117 + neo/prey/Prey/game_entityspawner.h | 25 + neo/prey/Prey/game_events.cpp | 12 + neo/prey/Prey/game_events.h | 18 + neo/prey/Prey/game_fixedpod.cpp | 209 + neo/prey/Prey/game_fixedpod.h | 31 + neo/prey/Prey/game_forcefield.cpp | 625 ++ neo/prey/Prey/game_forcefield.h | 94 + neo/prey/Prey/game_fxinfo.cpp | 343 + neo/prey/Prey/game_fxinfo.h | 175 + neo/prey/Prey/game_gibbable.cpp | 146 + neo/prey/Prey/game_gibbable.h | 31 + neo/prey/Prey/game_gravityswitch.cpp | 204 + neo/prey/Prey/game_gravityswitch.h | 35 + neo/prey/Prey/game_guihand.cpp | 52 + neo/prey/Prey/game_guihand.h | 33 + neo/prey/Prey/game_gun.cpp | 295 + neo/prey/Prey/game_gun.h | 46 + neo/prey/Prey/game_hand.cpp | 1155 +++ neo/prey/Prey/game_hand.h | 171 + neo/prey/Prey/game_handcontrol.cpp | 110 + neo/prey/Prey/game_handcontrol.h | 34 + neo/prey/Prey/game_healthbasin.cpp | 246 + neo/prey/Prey/game_healthbasin.h | 54 + neo/prey/Prey/game_healthspore.cpp | 121 + neo/prey/Prey/game_healthspore.h | 23 + neo/prey/Prey/game_inventory.cpp | 579 ++ neo/prey/Prey/game_inventory.h | 54 + neo/prey/Prey/game_itemautomatic.cpp | 227 + neo/prey/Prey/game_itemautomatic.h | 24 + neo/prey/Prey/game_itemcabinet.cpp | 417 + neo/prey/Prey/game_itemcabinet.h | 51 + neo/prey/Prey/game_jukebox.cpp | 265 + neo/prey/Prey/game_jukebox.h | 47 + neo/prey/Prey/game_jumpzone.cpp | 129 + neo/prey/Prey/game_jumpzone.h | 28 + neo/prey/Prey/game_light.cpp | 103 + neo/prey/Prey/game_light.h | 25 + neo/prey/Prey/game_lightfixture.cpp | 210 + neo/prey/Prey/game_lightfixture.h | 37 + neo/prey/Prey/game_mine.cpp | 432 + neo/prey/Prey/game_mine.h | 82 + neo/prey/Prey/game_misc.cpp | 404 + neo/prey/Prey/game_misc.h | 100 + neo/prey/Prey/game_modeldoor.cpp | 1076 +++ neo/prey/Prey/game_modeldoor.h | 139 + neo/prey/Prey/game_modeltoggle.cpp | 316 + neo/prey/Prey/game_modeltoggle.h | 62 + neo/prey/Prey/game_monster_ai.cpp | 2158 +++++ neo/prey/Prey/game_monster_ai.h | 262 + neo/prey/Prey/game_monster_ai_events.cpp | 1302 +++ neo/prey/Prey/game_mountedgun.cpp | 1048 +++ neo/prey/Prey/game_mountedgun.h | 158 + neo/prey/Prey/game_moveable.cpp | 492 + neo/prey/Prey/game_moveable.h | 56 + neo/prey/Prey/game_mover.cpp | 241 + neo/prey/Prey/game_mover.h | 73 + neo/prey/Prey/game_note.cpp | 29 + neo/prey/Prey/game_note.h | 13 + neo/prey/Prey/game_organtrigger.cpp | 190 + neo/prey/Prey/game_organtrigger.h | 41 + neo/prey/Prey/game_player.cpp | 7825 ++++++++++++++++ neo/prey/Prey/game_player.h | 771 ++ neo/prey/Prey/game_playerview.cpp | 748 ++ neo/prey/Prey/game_playerview.h | 108 + neo/prey/Prey/game_pod.cpp | 157 + neo/prey/Prey/game_pod.h | 34 + neo/prey/Prey/game_podspawner.cpp | 141 + neo/prey/Prey/game_podspawner.h | 31 + neo/prey/Prey/game_poker.cpp | 807 ++ neo/prey/Prey/game_poker.h | 123 + neo/prey/Prey/game_portal.cpp | 1168 +++ neo/prey/Prey/game_portal.h | 125 + neo/prey/Prey/game_portalframe.cpp | 20 + neo/prey/Prey/game_portalframe.h | 14 + neo/prey/Prey/game_proxdoor.cpp | 1163 +++ neo/prey/Prey/game_proxdoor.h | 204 + neo/prey/Prey/game_rail.cpp | 85 + neo/prey/Prey/game_rail.h | 41 + neo/prey/Prey/game_railshuttle.cpp | 620 ++ neo/prey/Prey/game_railshuttle.h | 106 + neo/prey/Prey/game_renderentity.cpp | 141 + neo/prey/Prey/game_renderentity.h | 31 + neo/prey/Prey/game_safeDeathVolume.cpp | 136 + neo/prey/Prey/game_safeDeathVolume.h | 43 + neo/prey/Prey/game_securityeye.cpp | 801 ++ neo/prey/Prey/game_securityeye.h | 134 + neo/prey/Prey/game_shuttle.cpp | 1057 +++ neo/prey/Prey/game_shuttle.h | 152 + neo/prey/Prey/game_shuttledock.cpp | 454 + neo/prey/Prey/game_shuttledock.h | 67 + neo/prey/Prey/game_shuttletransport.cpp | 244 + neo/prey/Prey/game_shuttletransport.h | 51 + neo/prey/Prey/game_skybox.cpp | 91 + neo/prey/Prey/game_skybox.h | 14 + neo/prey/Prey/game_slots.cpp | 417 + neo/prey/Prey/game_slots.h | 86 + neo/prey/Prey/game_sphere.cpp | 151 + neo/prey/Prey/game_sphere.h | 31 + neo/prey/Prey/game_spherepart.cpp | 319 + neo/prey/Prey/game_spherepart.h | 71 + neo/prey/Prey/game_spring.cpp | 191 + neo/prey/Prey/game_spring.h | 45 + neo/prey/Prey/game_sunCorona.cpp | 123 + neo/prey/Prey/game_sunCorona.h | 24 + neo/prey/Prey/game_talon.cpp | 1777 ++++ neo/prey/Prey/game_talon.h | 194 + neo/prey/Prey/game_targetproxy.cpp | 282 + neo/prey/Prey/game_targetproxy.h | 63 + neo/prey/Prey/game_targets.cpp | 849 ++ neo/prey/Prey/game_targets.h | 235 + neo/prey/Prey/game_trackmover.cpp | 172 + neo/prey/Prey/game_trackmover.h | 33 + neo/prey/Prey/game_trigger.cpp | 1482 +++ neo/prey/Prey/game_trigger.h | 258 + neo/prey/Prey/game_tripwire.cpp | 336 + neo/prey/Prey/game_tripwire.h | 50 + neo/prey/Prey/game_utils.cpp | 749 ++ neo/prey/Prey/game_utils.h | 318 + neo/prey/Prey/game_vehicle.cpp | 1922 ++++ neo/prey/Prey/game_vehicle.h | 375 + neo/prey/Prey/game_vomiter.cpp | 333 + neo/prey/Prey/game_vomiter.h | 61 + neo/prey/Prey/game_weaponHandState.cpp | 282 + neo/prey/Prey/game_weaponHandState.h | 41 + neo/prey/Prey/game_woundmanager.cpp | 314 + neo/prey/Prey/game_woundmanager.h | 34 + neo/prey/Prey/game_wraith.cpp | 974 ++ neo/prey/Prey/game_wraith.h | 128 + neo/prey/Prey/game_zone.cpp | 1182 +++ neo/prey/Prey/game_zone.h | 266 + neo/prey/Prey/particles_particles.cpp | 368 + neo/prey/Prey/particles_particles.h | 13 + neo/prey/Prey/physics_delta.cpp | 155 + neo/prey/Prey/physics_delta.h | 34 + neo/prey/Prey/physics_preyai.cpp | 573 ++ neo/prey/Prey/physics_preyai.h | 45 + neo/prey/Prey/physics_preyparametric.cpp | 302 + neo/prey/Prey/physics_preyparametric.h | 54 + neo/prey/Prey/physics_simple.cpp | 50 + neo/prey/Prey/physics_simple.h | 15 + neo/prey/Prey/physics_vehicle.cpp | 234 + neo/prey/Prey/physics_vehicle.h | 37 + neo/prey/Prey/prey_animator.cpp | 607 ++ neo/prey/Prey/prey_animator.h | 50 + neo/prey/Prey/prey_baseweapons.cpp | 2525 +++++ neo/prey/Prey/prey_baseweapons.h | 365 + neo/prey/Prey/prey_beam.cpp | 1150 +++ neo/prey/Prey/prey_beam.h | 206 + neo/prey/Prey/prey_bonecontroller.cpp | 247 + neo/prey/Prey/prey_bonecontroller.h | 57 + neo/prey/Prey/prey_camerainterpolator.cpp | 672 ++ neo/prey/Prey/prey_camerainterpolator.h | 134 + neo/prey/Prey/prey_firecontroller.cpp | 548 ++ neo/prey/Prey/prey_firecontroller.h | 160 + neo/prey/Prey/prey_game.cpp | 1438 +++ neo/prey/Prey/prey_game.h | 116 + neo/prey/Prey/prey_items.cpp | 639 ++ neo/prey/Prey/prey_items.h | 79 + neo/prey/Prey/prey_liquid.cpp | 105 + neo/prey/Prey/prey_liquid.h | 26 + neo/prey/Prey/prey_local.cpp | 3 + neo/prey/Prey/prey_local.h | 115 + neo/prey/Prey/prey_projectile.cpp | 1547 ++++ neo/prey/Prey/prey_projectile.h | 176 + neo/prey/Prey/prey_projectileautocannon.cpp | 21 + neo/prey/Prey/prey_projectileautocannon.h | 8 + neo/prey/Prey/prey_projectilebounce.cpp | 34 + neo/prey/Prey/prey_projectilebounce.h | 11 + neo/prey/Prey/prey_projectilebug.cpp | 135 + neo/prey/Prey/prey_projectilebug.h | 20 + neo/prey/Prey/prey_projectilebugtrigger.cpp | 40 + neo/prey/Prey/prey_projectilebugtrigger.h | 13 + neo/prey/Prey/prey_projectilecocoon.cpp | 105 + neo/prey/Prey/prey_projectilecocoon.h | 19 + .../Prey/prey_projectilecrawlergrenade.cpp | 557 ++ neo/prey/Prey/prey_projectilecrawlergrenade.h | 86 + neo/prey/Prey/prey_projectilefreezer.cpp | 115 + neo/prey/Prey/prey_projectilefreezer.h | 35 + neo/prey/Prey/prey_projectilegasbagpod.cpp | 89 + neo/prey/Prey/prey_projectilegasbagpod.h | 20 + neo/prey/Prey/prey_projectilehiderweapon.cpp | 136 + neo/prey/Prey/prey_projectilehiderweapon.h | 45 + neo/prey/Prey/prey_projectilemine.cpp | 505 + neo/prey/Prey/prey_projectilemine.h | 70 + neo/prey/Prey/prey_projectilerifle.cpp | 226 + neo/prey/Prey/prey_projectilerifle.h | 41 + .../Prey/prey_projectilerocketlauncher.cpp | 323 + neo/prey/Prey/prey_projectilerocketlauncher.h | 70 + neo/prey/Prey/prey_projectileshuttle.cpp | 22 + neo/prey/Prey/prey_projectileshuttle.h | 8 + neo/prey/Prey/prey_projectilesoulcannon.cpp | 143 + neo/prey/Prey/prey_projectilesoulcannon.h | 30 + neo/prey/Prey/prey_projectilespiritarrow.cpp | 105 + neo/prey/Prey/prey_projectilespiritarrow.h | 20 + neo/prey/Prey/prey_projectiletracking.cpp | 330 + neo/prey/Prey/prey_projectiletracking.h | 48 + neo/prey/Prey/prey_projectiletrigger.cpp | 56 + neo/prey/Prey/prey_projectiletrigger.h | 10 + neo/prey/Prey/prey_projectilewrench.cpp | 43 + neo/prey/Prey/prey_projectilewrench.h | 11 + neo/prey/Prey/prey_script_thread.cpp | 114 + neo/prey/Prey/prey_script_thread.h | 27 + neo/prey/Prey/prey_sound.cpp | 258 + neo/prey/Prey/prey_sound.h | 38 + neo/prey/Prey/prey_soundleadincontroller.cpp | 264 + neo/prey/Prey/prey_soundleadincontroller.h | 53 + neo/prey/Prey/prey_spiritbridge.cpp | 70 + neo/prey/Prey/prey_spiritbridge.h | 17 + neo/prey/Prey/prey_spiritproxy.cpp | 1094 +++ neo/prey/Prey/prey_spiritproxy.h | 200 + neo/prey/Prey/prey_spiritsecret.cpp | 72 + neo/prey/Prey/prey_spiritsecret.h | 15 + neo/prey/Prey/prey_vehiclefirecontroller.cpp | 225 + neo/prey/Prey/prey_vehiclefirecontroller.h | 47 + neo/prey/Prey/prey_weapon.cpp | 4 + neo/prey/Prey/prey_weapon.h | 5 + neo/prey/Prey/prey_weaponautocannon.cpp | 363 + neo/prey/Prey/prey_weaponautocannon.h | 67 + neo/prey/Prey/prey_weaponcrawlergrenade.cpp | 51 + neo/prey/Prey/prey_weaponcrawlergrenade.h | 21 + neo/prey/Prey/prey_weaponfirecontroller.cpp | 574 ++ neo/prey/Prey/prey_weaponfirecontroller.h | 138 + neo/prey/Prey/prey_weaponhider.cpp | 95 + neo/prey/Prey/prey_weaponhider.h | 42 + neo/prey/Prey/prey_weaponrifle.cpp | 466 + neo/prey/Prey/prey_weaponrifle.h | 91 + neo/prey/Prey/prey_weaponrocketlauncher.cpp | 29 + neo/prey/Prey/prey_weaponrocketlauncher.h | 97 + neo/prey/Prey/prey_weaponsoulstripper.cpp | 1427 +++ neo/prey/Prey/prey_weaponsoulstripper.h | 182 + neo/prey/Prey/prey_weaponspiritbow.cpp | 378 + neo/prey/Prey/prey_weaponspiritbow.h | 63 + neo/prey/Prey/sys_debugger.cpp | 1102 +++ neo/prey/Prey/sys_debugger.h | 187 + neo/prey/Prey/sys_preycmds.cpp | 1123 +++ neo/prey/Prey/sys_preycmds.h | 6 + neo/prey/PreyGame.vcxproj | 1061 +++ neo/prey/PreyGame.vcxproj.filters | 1437 +++ neo/prey/PreyGame.vcxproj.user | 4 + neo/prey/cm/CollisionModel.h | 146 + neo/prey/cm/CollisionModel_contacts.cpp | 48 + neo/prey/cm/CollisionModel_contents.cpp | 614 ++ neo/prey/cm/CollisionModel_debug.cpp | 498 + neo/prey/cm/CollisionModel_files.cpp | 598 ++ neo/prey/cm/CollisionModel_load.cpp | 3955 ++++++++ neo/prey/cm/CollisionModel_local.h | 527 ++ neo/prey/cm/CollisionModel_rotate.cpp | 1672 ++++ neo/prey/cm/CollisionModel_trace.cpp | 229 + neo/prey/cm/CollisionModel_translate.cpp | 1100 +++ neo/prey/framework/BuildVersion.h | 4 + neo/prey/framework/DeclPreyBeam.cpp | 348 + neo/prey/framework/DeclPreyBeam.h | 53 + neo/prey/game/AF.cpp | 1363 +++ neo/prey/game/AF.h | 99 + neo/prey/game/AFEntity.cpp | 3071 +++++++ neo/prey/game/AFEntity.h | 477 + neo/prey/game/Actor.cpp | 4136 +++++++++ neo/prey/game/Actor.h | 442 + neo/prey/game/BrittleFracture.cpp | 1360 +++ neo/prey/game/BrittleFracture.h | 116 + neo/prey/game/Camera.cpp | 747 ++ neo/prey/game/Camera.h | 109 + neo/prey/game/Entity.cpp | 6221 +++++++++++++ neo/prey/game/Entity.h | 789 ++ neo/prey/game/EntityAdditions.cpp | 1678 ++++ neo/prey/game/Fx.cpp | 826 ++ neo/prey/game/Fx.h | 106 + neo/prey/game/Game.def | 2 + neo/prey/game/Game.h | 342 + neo/prey/game/GameEdit.cpp | 1118 +++ neo/prey/game/GameEdit.h | 94 + neo/prey/game/Game_local.cpp | 5407 +++++++++++ neo/prey/game/Game_local.h | 1112 +++ neo/prey/game/Game_network.cpp | 2084 +++++ neo/prey/game/IK.cpp | 1156 +++ neo/prey/game/IK.h | 165 + neo/prey/game/Item.cpp | 1064 +++ neo/prey/game/Item.h | 155 + neo/prey/game/Light.cpp | 1132 +++ neo/prey/game/Light.h | 117 + neo/prey/game/Misc.cpp | 3198 +++++++ neo/prey/game/Misc.h | 751 ++ neo/prey/game/Moveable.cpp | 539 ++ neo/prey/game/Moveable.h | 99 + neo/prey/game/Mover.cpp | 4842 ++++++++++ neo/prey/game/Mover.h | 562 ++ neo/prey/game/MultiplayerGame.cpp | 4559 +++++++++ neo/prey/game/MultiplayerGame.h | 394 + neo/prey/game/Player.cpp | 7487 +++++++++++++++ neo/prey/game/Player.h | 709 ++ neo/prey/game/PlayerIcon.cpp | 284 + neo/prey/game/PlayerIcon.h | 46 + neo/prey/game/PlayerView.cpp | 710 ++ neo/prey/game/PlayerView.h | 110 + neo/prey/game/Pvs.cpp | 1551 ++++ neo/prey/game/Pvs.h | 110 + neo/prey/game/SecurityCamera.cpp | 563 ++ neo/prey/game/SecurityCamera.h | 72 + neo/prey/game/SmokeParticles.cpp | 487 + neo/prey/game/SmokeParticles.h | 77 + neo/prey/game/Sound.cpp | 280 + neo/prey/game/Sound.h | 51 + neo/prey/game/Target.cpp | 1048 +++ neo/prey/game/Target.h | 414 + neo/prey/game/Weapon.cpp | 3181 +++++++ neo/prey/game/Weapon.h | 388 + neo/prey/game/WorldSpawn.cpp | 125 + neo/prey/game/WorldSpawn.h | 30 + neo/prey/game/ai/AAS.cpp | 250 + neo/prey/game/ai/AAS.h | 118 + neo/prey/game/ai/AAS_NearPoint.cpp | 223 + neo/prey/game/ai/AAS_debug.cpp | 568 ++ neo/prey/game/ai/AAS_local.h | 224 + neo/prey/game/ai/AAS_pathing.cpp | 696 ++ neo/prey/game/ai/AAS_routing.cpp | 1324 +++ neo/prey/game/ai/AI.cpp | 5454 +++++++++++ neo/prey/game/ai/AI.h | 781 ++ neo/prey/game/ai/AI_events.cpp | 2815 ++++++ neo/prey/game/ai/AI_pathing.cpp | 1528 +++ neo/prey/game/anim/Anim.cpp | 1259 +++ neo/prey/game/anim/Anim.h | 709 ++ neo/prey/game/anim/Anim_Blend.cpp | 5166 +++++++++++ neo/prey/game/anim/Anim_Import.cpp | 554 ++ neo/prey/game/anim/Anim_Testmodel.cpp | 935 ++ neo/prey/game/anim/Anim_Testmodel.h | 70 + neo/prey/game/gamesys/Callbacks.cpp | 2600 ++++++ neo/prey/game/gamesys/Class.cpp | 1113 +++ neo/prey/game/gamesys/Class.h | 341 + neo/prey/game/gamesys/DebugGraph.cpp | 67 + neo/prey/game/gamesys/DebugGraph.h | 15 + neo/prey/game/gamesys/Event.cpp | 903 ++ neo/prey/game/gamesys/Event.h | 196 + neo/prey/game/gamesys/SaveGame.cpp | 1663 ++++ neo/prey/game/gamesys/SaveGame.h | 167 + neo/prey/game/gamesys/SysCmds.cpp | 2841 ++++++ neo/prey/game/gamesys/SysCmds.h | 9 + neo/prey/game/gamesys/SysCvar.cpp | 401 + neo/prey/game/gamesys/SysCvar.h | 294 + neo/prey/game/physics/Clip.cpp | 2108 +++++ neo/prey/game/physics/Clip.h | 397 + neo/prey/game/physics/Force.cpp | 68 + neo/prey/game/physics/Force.h | 41 + neo/prey/game/physics/Force_Constant.cpp | 110 + neo/prey/game/physics/Force_Constant.h | 46 + neo/prey/game/physics/Force_Drag.cpp | 130 + neo/prey/game/physics/Force_Drag.h | 49 + neo/prey/game/physics/Force_Field.cpp | 243 + neo/prey/game/physics/Force_Field.h | 69 + neo/prey/game/physics/Force_Spring.cpp | 183 + neo/prey/game/physics/Force_Spring.h | 55 + neo/prey/game/physics/Physics.cpp | 55 + neo/prey/game/physics/Physics.h | 162 + neo/prey/game/physics/Physics_AF.cpp | 8167 +++++++++++++++++ neo/prey/game/physics/Physics_AF.h | 1036 +++ neo/prey/game/physics/Physics_Actor.cpp | 671 ++ neo/prey/game/physics/Physics_Actor.h | 133 + neo/prey/game/physics/Physics_Base.cpp | 844 ++ neo/prey/game/physics/Physics_Base.h | 145 + neo/prey/game/physics/Physics_Monster.cpp | 809 ++ neo/prey/game/physics/Physics_Monster.h | 129 + neo/prey/game/physics/Physics_Parametric.cpp | 1291 +++ neo/prey/game/physics/Physics_Parametric.h | 160 + neo/prey/game/physics/Physics_Player.cpp | 2116 +++++ neo/prey/game/physics/Physics_Player.h | 220 + neo/prey/game/physics/Physics_PreyPlayer.cpp | 1172 +++ neo/prey/game/physics/Physics_PreyPlayer.h | 122 + neo/prey/game/physics/Physics_RigidBody.cpp | 1601 ++++ neo/prey/game/physics/Physics_RigidBody.h | 179 + neo/prey/game/physics/Physics_Static.cpp | 836 ++ neo/prey/game/physics/Physics_Static.h | 136 + neo/prey/game/physics/Physics_StaticMulti.cpp | 1025 +++ neo/prey/game/physics/Physics_StaticMulti.h | 129 + neo/prey/game/physics/Push.cpp | 1484 +++ neo/prey/game/physics/Push.h | 91 + neo/prey/game/projectile.cpp | 2165 +++++ neo/prey/game/projectile.h | 222 + neo/prey/game/script/Script_Compiler.cpp | 2628 ++++++ neo/prey/game/script/Script_Compiler.h | 253 + neo/prey/game/script/Script_Interpreter.cpp | 2016 ++++ neo/prey/game/script/Script_Interpreter.h | 251 + neo/prey/game/script/Script_Program.cpp | 2264 +++++ neo/prey/game/script/Script_Program.h | 732 ++ neo/prey/game/script/Script_Thread.cpp | 2171 +++++ neo/prey/game/script/Script_Thread.h | 346 + neo/prey/game/trigger.cpp | 1167 +++ neo/prey/game/trigger.h | 264 + neo/prey/idLib/MsgQueue.cpp | 234 + neo/prey/idLib/MsgQueue.h | 38 + neo/prey/idLib/containers/PreyStack.h | 64 + neo/prey/idLib/math/prey_interpolate.h | 346 + neo/prey/idLib/math/prey_math.cpp | 248 + neo/prey/idLib/math/prey_math.h | 56 + neo/prey/idLib/preylib.h | 81 + neo/renderer/Material.cpp | 68 + neo/renderer/Material.h | 48 +- neo/renderer/Model.h | 2 + neo/renderer/Model_local.h | 3 +- neo/renderer/RenderSystem.h | 10 +- neo/renderer/RenderWorld.cpp | 1 + neo/renderer/RenderWorld.h | 68 +- neo/renderer/RenderWorld_demo.cpp | 4 + neo/sound/snd_emitter.cpp | 31 + neo/sound/snd_local.h | 15 + neo/sound/snd_shader.cpp | 466 +- neo/sound/snd_system.cpp | 140 + neo/sound/sound.h | 59 + neo/sys/sys_public.h | 15 + neo/sys/win32/rc/doom.rc | 5 + neo/sys/win32/rc/res/prey.ico | Bin 0 -> 7358 bytes neo/tools/radiant/CamWnd.cpp | 2 + neo/ui/UserInterface.h | 2 + preybase/pk4s_go_here | 0 552 files changed, 265551 insertions(+), 209 deletions(-) create mode 100644 neo/cm/PreyCM/CollisionModel.h create mode 100644 neo/cm/PreyCM/CollisionModel_contacts.cpp create mode 100644 neo/cm/PreyCM/CollisionModel_contents.cpp create mode 100644 neo/cm/PreyCM/CollisionModel_debug.cpp create mode 100644 neo/cm/PreyCM/CollisionModel_files.cpp create mode 100644 neo/cm/PreyCM/CollisionModel_load.cpp create mode 100644 neo/cm/PreyCM/CollisionModel_local.h create mode 100644 neo/cm/PreyCM/CollisionModel_rotate.cpp create mode 100644 neo/cm/PreyCM/CollisionModel_trace.cpp create mode 100644 neo/cm/PreyCM/CollisionModel_translate.cpp create mode 100644 neo/prey/MayaImport/Maya4.5/maya.h create mode 100644 neo/prey/MayaImport/Maya6.0/maya.h create mode 100644 neo/prey/MayaImport/exporter.h create mode 100644 neo/prey/MayaImport/maya5.0/maya.h create mode 100644 neo/prey/MayaImport/maya_main.cpp create mode 100644 neo/prey/MayaImport/maya_main.h create mode 100644 neo/prey/MayaImport/mayaimport.def create mode 100644 neo/prey/Prey/ai_Navigator.cpp create mode 100644 neo/prey/Prey/ai_Navigator.h create mode 100644 neo/prey/Prey/ai_centurion.cpp create mode 100644 neo/prey/Prey/ai_centurion.h create mode 100644 neo/prey/Prey/ai_crawler.cpp create mode 100644 neo/prey/Prey/ai_crawler.h create mode 100644 neo/prey/Prey/ai_creaturex.cpp create mode 100644 neo/prey/Prey/ai_creaturex.h create mode 100644 neo/prey/Prey/ai_droid.cpp create mode 100644 neo/prey/Prey/ai_droid.h create mode 100644 neo/prey/Prey/ai_gasbag_simple.cpp create mode 100644 neo/prey/Prey/ai_gasbag_simple.h create mode 100644 neo/prey/Prey/ai_harvester_simple.cpp create mode 100644 neo/prey/Prey/ai_harvester_simple.h create mode 100644 neo/prey/Prey/ai_hunter_simple.cpp create mode 100644 neo/prey/Prey/ai_hunter_simple.h create mode 100644 neo/prey/Prey/ai_inspector.cpp create mode 100644 neo/prey/Prey/ai_inspector.h create mode 100644 neo/prey/Prey/ai_jetpack_harvester_simple.cpp create mode 100644 neo/prey/Prey/ai_jetpack_harvester_simple.h create mode 100644 neo/prey/Prey/ai_keeper_simple.cpp create mode 100644 neo/prey/Prey/ai_keeper_simple.h create mode 100644 neo/prey/Prey/ai_mutate.cpp create mode 100644 neo/prey/Prey/ai_mutate.h create mode 100644 neo/prey/Prey/ai_mutilatedhuman.cpp create mode 100644 neo/prey/Prey/ai_mutilatedhuman.h create mode 100644 neo/prey/Prey/ai_passageway.cpp create mode 100644 neo/prey/Prey/ai_passageway.h create mode 100644 neo/prey/Prey/ai_possessedTommy.cpp create mode 100644 neo/prey/Prey/ai_possessedTommy.h create mode 100644 neo/prey/Prey/ai_reaction.cpp create mode 100644 neo/prey/Prey/ai_reaction.h create mode 100644 neo/prey/Prey/ai_spawncase.cpp create mode 100644 neo/prey/Prey/ai_spawncase.h create mode 100644 neo/prey/Prey/ai_speech.cpp create mode 100644 neo/prey/Prey/ai_speech.h create mode 100644 neo/prey/Prey/ai_sphereboss.cpp create mode 100644 neo/prey/Prey/ai_sphereboss.h create mode 100644 neo/prey/Prey/anim_baseanim.cpp create mode 100644 neo/prey/Prey/anim_baseanim.h create mode 100644 neo/prey/Prey/force_converge.cpp create mode 100644 neo/prey/Prey/force_converge.h create mode 100644 neo/prey/Prey/game_afs.cpp create mode 100644 neo/prey/Prey/game_afs.h create mode 100644 neo/prey/Prey/game_alarm.cpp create mode 100644 neo/prey/Prey/game_alarm.h create mode 100644 neo/prey/Prey/game_anim.cpp create mode 100644 neo/prey/Prey/game_anim.h create mode 100644 neo/prey/Prey/game_animBlend.cpp create mode 100644 neo/prey/Prey/game_animBlend.h create mode 100644 neo/prey/Prey/game_animDriven.cpp create mode 100644 neo/prey/Prey/game_animDriven.h create mode 100644 neo/prey/Prey/game_animatedentity.cpp create mode 100644 neo/prey/Prey/game_animatedentity.h create mode 100644 neo/prey/Prey/game_animatedgui.cpp create mode 100644 neo/prey/Prey/game_animatedgui.h create mode 100644 neo/prey/Prey/game_animator.cpp create mode 100644 neo/prey/Prey/game_animator.h create mode 100644 neo/prey/Prey/game_arcadegame.cpp create mode 100644 neo/prey/Prey/game_arcadegame.h create mode 100644 neo/prey/Prey/game_barrel.cpp create mode 100644 neo/prey/Prey/game_barrel.h create mode 100644 neo/prey/Prey/game_bindController.cpp create mode 100644 neo/prey/Prey/game_bindController.h create mode 100644 neo/prey/Prey/game_blackjack.cpp create mode 100644 neo/prey/Prey/game_blackjack.h create mode 100644 neo/prey/Prey/game_cards.cpp create mode 100644 neo/prey/Prey/game_cards.h create mode 100644 neo/prey/Prey/game_cilia.cpp create mode 100644 neo/prey/Prey/game_cilia.h create mode 100644 neo/prey/Prey/game_console.cpp create mode 100644 neo/prey/Prey/game_console.h create mode 100644 neo/prey/Prey/game_damagetester.cpp create mode 100644 neo/prey/Prey/game_damagetester.h create mode 100644 neo/prey/Prey/game_dda.cpp create mode 100644 neo/prey/Prey/game_dda.h create mode 100644 neo/prey/Prey/game_deathwraith.cpp create mode 100644 neo/prey/Prey/game_deathwraith.h create mode 100644 neo/prey/Prey/game_debrisspawner.cpp create mode 100644 neo/prey/Prey/game_debrisspawner.h create mode 100644 neo/prey/Prey/game_dock.cpp create mode 100644 neo/prey/Prey/game_dock.h create mode 100644 neo/prey/Prey/game_dockedgun.cpp create mode 100644 neo/prey/Prey/game_dockedgun.h create mode 100644 neo/prey/Prey/game_door.cpp create mode 100644 neo/prey/Prey/game_door.h create mode 100644 neo/prey/Prey/game_eggspawner.cpp create mode 100644 neo/prey/Prey/game_eggspawner.h create mode 100644 neo/prey/Prey/game_energynode.cpp create mode 100644 neo/prey/Prey/game_energynode.h create mode 100644 neo/prey/Prey/game_entityfx.cpp create mode 100644 neo/prey/Prey/game_entityfx.h create mode 100644 neo/prey/Prey/game_entityspawner.cpp create mode 100644 neo/prey/Prey/game_entityspawner.h create mode 100644 neo/prey/Prey/game_events.cpp create mode 100644 neo/prey/Prey/game_events.h create mode 100644 neo/prey/Prey/game_fixedpod.cpp create mode 100644 neo/prey/Prey/game_fixedpod.h create mode 100644 neo/prey/Prey/game_forcefield.cpp create mode 100644 neo/prey/Prey/game_forcefield.h create mode 100644 neo/prey/Prey/game_fxinfo.cpp create mode 100644 neo/prey/Prey/game_fxinfo.h create mode 100644 neo/prey/Prey/game_gibbable.cpp create mode 100644 neo/prey/Prey/game_gibbable.h create mode 100644 neo/prey/Prey/game_gravityswitch.cpp create mode 100644 neo/prey/Prey/game_gravityswitch.h create mode 100644 neo/prey/Prey/game_guihand.cpp create mode 100644 neo/prey/Prey/game_guihand.h create mode 100644 neo/prey/Prey/game_gun.cpp create mode 100644 neo/prey/Prey/game_gun.h create mode 100644 neo/prey/Prey/game_hand.cpp create mode 100644 neo/prey/Prey/game_hand.h create mode 100644 neo/prey/Prey/game_handcontrol.cpp create mode 100644 neo/prey/Prey/game_handcontrol.h create mode 100644 neo/prey/Prey/game_healthbasin.cpp create mode 100644 neo/prey/Prey/game_healthbasin.h create mode 100644 neo/prey/Prey/game_healthspore.cpp create mode 100644 neo/prey/Prey/game_healthspore.h create mode 100644 neo/prey/Prey/game_inventory.cpp create mode 100644 neo/prey/Prey/game_inventory.h create mode 100644 neo/prey/Prey/game_itemautomatic.cpp create mode 100644 neo/prey/Prey/game_itemautomatic.h create mode 100644 neo/prey/Prey/game_itemcabinet.cpp create mode 100644 neo/prey/Prey/game_itemcabinet.h create mode 100644 neo/prey/Prey/game_jukebox.cpp create mode 100644 neo/prey/Prey/game_jukebox.h create mode 100644 neo/prey/Prey/game_jumpzone.cpp create mode 100644 neo/prey/Prey/game_jumpzone.h create mode 100644 neo/prey/Prey/game_light.cpp create mode 100644 neo/prey/Prey/game_light.h create mode 100644 neo/prey/Prey/game_lightfixture.cpp create mode 100644 neo/prey/Prey/game_lightfixture.h create mode 100644 neo/prey/Prey/game_mine.cpp create mode 100644 neo/prey/Prey/game_mine.h create mode 100644 neo/prey/Prey/game_misc.cpp create mode 100644 neo/prey/Prey/game_misc.h create mode 100644 neo/prey/Prey/game_modeldoor.cpp create mode 100644 neo/prey/Prey/game_modeldoor.h create mode 100644 neo/prey/Prey/game_modeltoggle.cpp create mode 100644 neo/prey/Prey/game_modeltoggle.h create mode 100644 neo/prey/Prey/game_monster_ai.cpp create mode 100644 neo/prey/Prey/game_monster_ai.h create mode 100644 neo/prey/Prey/game_monster_ai_events.cpp create mode 100644 neo/prey/Prey/game_mountedgun.cpp create mode 100644 neo/prey/Prey/game_mountedgun.h create mode 100644 neo/prey/Prey/game_moveable.cpp create mode 100644 neo/prey/Prey/game_moveable.h create mode 100644 neo/prey/Prey/game_mover.cpp create mode 100644 neo/prey/Prey/game_mover.h create mode 100644 neo/prey/Prey/game_note.cpp create mode 100644 neo/prey/Prey/game_note.h create mode 100644 neo/prey/Prey/game_organtrigger.cpp create mode 100644 neo/prey/Prey/game_organtrigger.h create mode 100644 neo/prey/Prey/game_player.cpp create mode 100644 neo/prey/Prey/game_player.h create mode 100644 neo/prey/Prey/game_playerview.cpp create mode 100644 neo/prey/Prey/game_playerview.h create mode 100644 neo/prey/Prey/game_pod.cpp create mode 100644 neo/prey/Prey/game_pod.h create mode 100644 neo/prey/Prey/game_podspawner.cpp create mode 100644 neo/prey/Prey/game_podspawner.h create mode 100644 neo/prey/Prey/game_poker.cpp create mode 100644 neo/prey/Prey/game_poker.h create mode 100644 neo/prey/Prey/game_portal.cpp create mode 100644 neo/prey/Prey/game_portal.h create mode 100644 neo/prey/Prey/game_portalframe.cpp create mode 100644 neo/prey/Prey/game_portalframe.h create mode 100644 neo/prey/Prey/game_proxdoor.cpp create mode 100644 neo/prey/Prey/game_proxdoor.h create mode 100644 neo/prey/Prey/game_rail.cpp create mode 100644 neo/prey/Prey/game_rail.h create mode 100644 neo/prey/Prey/game_railshuttle.cpp create mode 100644 neo/prey/Prey/game_railshuttle.h create mode 100644 neo/prey/Prey/game_renderentity.cpp create mode 100644 neo/prey/Prey/game_renderentity.h create mode 100644 neo/prey/Prey/game_safeDeathVolume.cpp create mode 100644 neo/prey/Prey/game_safeDeathVolume.h create mode 100644 neo/prey/Prey/game_securityeye.cpp create mode 100644 neo/prey/Prey/game_securityeye.h create mode 100644 neo/prey/Prey/game_shuttle.cpp create mode 100644 neo/prey/Prey/game_shuttle.h create mode 100644 neo/prey/Prey/game_shuttledock.cpp create mode 100644 neo/prey/Prey/game_shuttledock.h create mode 100644 neo/prey/Prey/game_shuttletransport.cpp create mode 100644 neo/prey/Prey/game_shuttletransport.h create mode 100644 neo/prey/Prey/game_skybox.cpp create mode 100644 neo/prey/Prey/game_skybox.h create mode 100644 neo/prey/Prey/game_slots.cpp create mode 100644 neo/prey/Prey/game_slots.h create mode 100644 neo/prey/Prey/game_sphere.cpp create mode 100644 neo/prey/Prey/game_sphere.h create mode 100644 neo/prey/Prey/game_spherepart.cpp create mode 100644 neo/prey/Prey/game_spherepart.h create mode 100644 neo/prey/Prey/game_spring.cpp create mode 100644 neo/prey/Prey/game_spring.h create mode 100644 neo/prey/Prey/game_sunCorona.cpp create mode 100644 neo/prey/Prey/game_sunCorona.h create mode 100644 neo/prey/Prey/game_talon.cpp create mode 100644 neo/prey/Prey/game_talon.h create mode 100644 neo/prey/Prey/game_targetproxy.cpp create mode 100644 neo/prey/Prey/game_targetproxy.h create mode 100644 neo/prey/Prey/game_targets.cpp create mode 100644 neo/prey/Prey/game_targets.h create mode 100644 neo/prey/Prey/game_trackmover.cpp create mode 100644 neo/prey/Prey/game_trackmover.h create mode 100644 neo/prey/Prey/game_trigger.cpp create mode 100644 neo/prey/Prey/game_trigger.h create mode 100644 neo/prey/Prey/game_tripwire.cpp create mode 100644 neo/prey/Prey/game_tripwire.h create mode 100644 neo/prey/Prey/game_utils.cpp create mode 100644 neo/prey/Prey/game_utils.h create mode 100644 neo/prey/Prey/game_vehicle.cpp create mode 100644 neo/prey/Prey/game_vehicle.h create mode 100644 neo/prey/Prey/game_vomiter.cpp create mode 100644 neo/prey/Prey/game_vomiter.h create mode 100644 neo/prey/Prey/game_weaponHandState.cpp create mode 100644 neo/prey/Prey/game_weaponHandState.h create mode 100644 neo/prey/Prey/game_woundmanager.cpp create mode 100644 neo/prey/Prey/game_woundmanager.h create mode 100644 neo/prey/Prey/game_wraith.cpp create mode 100644 neo/prey/Prey/game_wraith.h create mode 100644 neo/prey/Prey/game_zone.cpp create mode 100644 neo/prey/Prey/game_zone.h create mode 100644 neo/prey/Prey/particles_particles.cpp create mode 100644 neo/prey/Prey/particles_particles.h create mode 100644 neo/prey/Prey/physics_delta.cpp create mode 100644 neo/prey/Prey/physics_delta.h create mode 100644 neo/prey/Prey/physics_preyai.cpp create mode 100644 neo/prey/Prey/physics_preyai.h create mode 100644 neo/prey/Prey/physics_preyparametric.cpp create mode 100644 neo/prey/Prey/physics_preyparametric.h create mode 100644 neo/prey/Prey/physics_simple.cpp create mode 100644 neo/prey/Prey/physics_simple.h create mode 100644 neo/prey/Prey/physics_vehicle.cpp create mode 100644 neo/prey/Prey/physics_vehicle.h create mode 100644 neo/prey/Prey/prey_animator.cpp create mode 100644 neo/prey/Prey/prey_animator.h create mode 100644 neo/prey/Prey/prey_baseweapons.cpp create mode 100644 neo/prey/Prey/prey_baseweapons.h create mode 100644 neo/prey/Prey/prey_beam.cpp create mode 100644 neo/prey/Prey/prey_beam.h create mode 100644 neo/prey/Prey/prey_bonecontroller.cpp create mode 100644 neo/prey/Prey/prey_bonecontroller.h create mode 100644 neo/prey/Prey/prey_camerainterpolator.cpp create mode 100644 neo/prey/Prey/prey_camerainterpolator.h create mode 100644 neo/prey/Prey/prey_firecontroller.cpp create mode 100644 neo/prey/Prey/prey_firecontroller.h create mode 100644 neo/prey/Prey/prey_game.cpp create mode 100644 neo/prey/Prey/prey_game.h create mode 100644 neo/prey/Prey/prey_items.cpp create mode 100644 neo/prey/Prey/prey_items.h create mode 100644 neo/prey/Prey/prey_liquid.cpp create mode 100644 neo/prey/Prey/prey_liquid.h create mode 100644 neo/prey/Prey/prey_local.cpp create mode 100644 neo/prey/Prey/prey_local.h create mode 100644 neo/prey/Prey/prey_projectile.cpp create mode 100644 neo/prey/Prey/prey_projectile.h create mode 100644 neo/prey/Prey/prey_projectileautocannon.cpp create mode 100644 neo/prey/Prey/prey_projectileautocannon.h create mode 100644 neo/prey/Prey/prey_projectilebounce.cpp create mode 100644 neo/prey/Prey/prey_projectilebounce.h create mode 100644 neo/prey/Prey/prey_projectilebug.cpp create mode 100644 neo/prey/Prey/prey_projectilebug.h create mode 100644 neo/prey/Prey/prey_projectilebugtrigger.cpp create mode 100644 neo/prey/Prey/prey_projectilebugtrigger.h create mode 100644 neo/prey/Prey/prey_projectilecocoon.cpp create mode 100644 neo/prey/Prey/prey_projectilecocoon.h create mode 100644 neo/prey/Prey/prey_projectilecrawlergrenade.cpp create mode 100644 neo/prey/Prey/prey_projectilecrawlergrenade.h create mode 100644 neo/prey/Prey/prey_projectilefreezer.cpp create mode 100644 neo/prey/Prey/prey_projectilefreezer.h create mode 100644 neo/prey/Prey/prey_projectilegasbagpod.cpp create mode 100644 neo/prey/Prey/prey_projectilegasbagpod.h create mode 100644 neo/prey/Prey/prey_projectilehiderweapon.cpp create mode 100644 neo/prey/Prey/prey_projectilehiderweapon.h create mode 100644 neo/prey/Prey/prey_projectilemine.cpp create mode 100644 neo/prey/Prey/prey_projectilemine.h create mode 100644 neo/prey/Prey/prey_projectilerifle.cpp create mode 100644 neo/prey/Prey/prey_projectilerifle.h create mode 100644 neo/prey/Prey/prey_projectilerocketlauncher.cpp create mode 100644 neo/prey/Prey/prey_projectilerocketlauncher.h create mode 100644 neo/prey/Prey/prey_projectileshuttle.cpp create mode 100644 neo/prey/Prey/prey_projectileshuttle.h create mode 100644 neo/prey/Prey/prey_projectilesoulcannon.cpp create mode 100644 neo/prey/Prey/prey_projectilesoulcannon.h create mode 100644 neo/prey/Prey/prey_projectilespiritarrow.cpp create mode 100644 neo/prey/Prey/prey_projectilespiritarrow.h create mode 100644 neo/prey/Prey/prey_projectiletracking.cpp create mode 100644 neo/prey/Prey/prey_projectiletracking.h create mode 100644 neo/prey/Prey/prey_projectiletrigger.cpp create mode 100644 neo/prey/Prey/prey_projectiletrigger.h create mode 100644 neo/prey/Prey/prey_projectilewrench.cpp create mode 100644 neo/prey/Prey/prey_projectilewrench.h create mode 100644 neo/prey/Prey/prey_script_thread.cpp create mode 100644 neo/prey/Prey/prey_script_thread.h create mode 100644 neo/prey/Prey/prey_sound.cpp create mode 100644 neo/prey/Prey/prey_sound.h create mode 100644 neo/prey/Prey/prey_soundleadincontroller.cpp create mode 100644 neo/prey/Prey/prey_soundleadincontroller.h create mode 100644 neo/prey/Prey/prey_spiritbridge.cpp create mode 100644 neo/prey/Prey/prey_spiritbridge.h create mode 100644 neo/prey/Prey/prey_spiritproxy.cpp create mode 100644 neo/prey/Prey/prey_spiritproxy.h create mode 100644 neo/prey/Prey/prey_spiritsecret.cpp create mode 100644 neo/prey/Prey/prey_spiritsecret.h create mode 100644 neo/prey/Prey/prey_vehiclefirecontroller.cpp create mode 100644 neo/prey/Prey/prey_vehiclefirecontroller.h create mode 100644 neo/prey/Prey/prey_weapon.cpp create mode 100644 neo/prey/Prey/prey_weapon.h create mode 100644 neo/prey/Prey/prey_weaponautocannon.cpp create mode 100644 neo/prey/Prey/prey_weaponautocannon.h create mode 100644 neo/prey/Prey/prey_weaponcrawlergrenade.cpp create mode 100644 neo/prey/Prey/prey_weaponcrawlergrenade.h create mode 100644 neo/prey/Prey/prey_weaponfirecontroller.cpp create mode 100644 neo/prey/Prey/prey_weaponfirecontroller.h create mode 100644 neo/prey/Prey/prey_weaponhider.cpp create mode 100644 neo/prey/Prey/prey_weaponhider.h create mode 100644 neo/prey/Prey/prey_weaponrifle.cpp create mode 100644 neo/prey/Prey/prey_weaponrifle.h create mode 100644 neo/prey/Prey/prey_weaponrocketlauncher.cpp create mode 100644 neo/prey/Prey/prey_weaponrocketlauncher.h create mode 100644 neo/prey/Prey/prey_weaponsoulstripper.cpp create mode 100644 neo/prey/Prey/prey_weaponsoulstripper.h create mode 100644 neo/prey/Prey/prey_weaponspiritbow.cpp create mode 100644 neo/prey/Prey/prey_weaponspiritbow.h create mode 100644 neo/prey/Prey/sys_debugger.cpp create mode 100644 neo/prey/Prey/sys_debugger.h create mode 100644 neo/prey/Prey/sys_preycmds.cpp create mode 100644 neo/prey/Prey/sys_preycmds.h create mode 100644 neo/prey/PreyGame.vcxproj create mode 100644 neo/prey/PreyGame.vcxproj.filters create mode 100644 neo/prey/PreyGame.vcxproj.user create mode 100644 neo/prey/cm/CollisionModel.h create mode 100644 neo/prey/cm/CollisionModel_contacts.cpp create mode 100644 neo/prey/cm/CollisionModel_contents.cpp create mode 100644 neo/prey/cm/CollisionModel_debug.cpp create mode 100644 neo/prey/cm/CollisionModel_files.cpp create mode 100644 neo/prey/cm/CollisionModel_load.cpp create mode 100644 neo/prey/cm/CollisionModel_local.h create mode 100644 neo/prey/cm/CollisionModel_rotate.cpp create mode 100644 neo/prey/cm/CollisionModel_trace.cpp create mode 100644 neo/prey/cm/CollisionModel_translate.cpp create mode 100644 neo/prey/framework/BuildVersion.h create mode 100644 neo/prey/framework/DeclPreyBeam.cpp create mode 100644 neo/prey/framework/DeclPreyBeam.h create mode 100644 neo/prey/game/AF.cpp create mode 100644 neo/prey/game/AF.h create mode 100644 neo/prey/game/AFEntity.cpp create mode 100644 neo/prey/game/AFEntity.h create mode 100644 neo/prey/game/Actor.cpp create mode 100644 neo/prey/game/Actor.h create mode 100644 neo/prey/game/BrittleFracture.cpp create mode 100644 neo/prey/game/BrittleFracture.h create mode 100644 neo/prey/game/Camera.cpp create mode 100644 neo/prey/game/Camera.h create mode 100644 neo/prey/game/Entity.cpp create mode 100644 neo/prey/game/Entity.h create mode 100644 neo/prey/game/EntityAdditions.cpp create mode 100644 neo/prey/game/Fx.cpp create mode 100644 neo/prey/game/Fx.h create mode 100644 neo/prey/game/Game.def create mode 100644 neo/prey/game/Game.h create mode 100644 neo/prey/game/GameEdit.cpp create mode 100644 neo/prey/game/GameEdit.h create mode 100644 neo/prey/game/Game_local.cpp create mode 100644 neo/prey/game/Game_local.h create mode 100644 neo/prey/game/Game_network.cpp create mode 100644 neo/prey/game/IK.cpp create mode 100644 neo/prey/game/IK.h create mode 100644 neo/prey/game/Item.cpp create mode 100644 neo/prey/game/Item.h create mode 100644 neo/prey/game/Light.cpp create mode 100644 neo/prey/game/Light.h create mode 100644 neo/prey/game/Misc.cpp create mode 100644 neo/prey/game/Misc.h create mode 100644 neo/prey/game/Moveable.cpp create mode 100644 neo/prey/game/Moveable.h create mode 100644 neo/prey/game/Mover.cpp create mode 100644 neo/prey/game/Mover.h create mode 100644 neo/prey/game/MultiplayerGame.cpp create mode 100644 neo/prey/game/MultiplayerGame.h create mode 100644 neo/prey/game/Player.cpp create mode 100644 neo/prey/game/Player.h create mode 100644 neo/prey/game/PlayerIcon.cpp create mode 100644 neo/prey/game/PlayerIcon.h create mode 100644 neo/prey/game/PlayerView.cpp create mode 100644 neo/prey/game/PlayerView.h create mode 100644 neo/prey/game/Pvs.cpp create mode 100644 neo/prey/game/Pvs.h create mode 100644 neo/prey/game/SecurityCamera.cpp create mode 100644 neo/prey/game/SecurityCamera.h create mode 100644 neo/prey/game/SmokeParticles.cpp create mode 100644 neo/prey/game/SmokeParticles.h create mode 100644 neo/prey/game/Sound.cpp create mode 100644 neo/prey/game/Sound.h create mode 100644 neo/prey/game/Target.cpp create mode 100644 neo/prey/game/Target.h create mode 100644 neo/prey/game/Weapon.cpp create mode 100644 neo/prey/game/Weapon.h create mode 100644 neo/prey/game/WorldSpawn.cpp create mode 100644 neo/prey/game/WorldSpawn.h create mode 100644 neo/prey/game/ai/AAS.cpp create mode 100644 neo/prey/game/ai/AAS.h create mode 100644 neo/prey/game/ai/AAS_NearPoint.cpp create mode 100644 neo/prey/game/ai/AAS_debug.cpp create mode 100644 neo/prey/game/ai/AAS_local.h create mode 100644 neo/prey/game/ai/AAS_pathing.cpp create mode 100644 neo/prey/game/ai/AAS_routing.cpp create mode 100644 neo/prey/game/ai/AI.cpp create mode 100644 neo/prey/game/ai/AI.h create mode 100644 neo/prey/game/ai/AI_events.cpp create mode 100644 neo/prey/game/ai/AI_pathing.cpp create mode 100644 neo/prey/game/anim/Anim.cpp create mode 100644 neo/prey/game/anim/Anim.h create mode 100644 neo/prey/game/anim/Anim_Blend.cpp create mode 100644 neo/prey/game/anim/Anim_Import.cpp create mode 100644 neo/prey/game/anim/Anim_Testmodel.cpp create mode 100644 neo/prey/game/anim/Anim_Testmodel.h create mode 100644 neo/prey/game/gamesys/Callbacks.cpp create mode 100644 neo/prey/game/gamesys/Class.cpp create mode 100644 neo/prey/game/gamesys/Class.h create mode 100644 neo/prey/game/gamesys/DebugGraph.cpp create mode 100644 neo/prey/game/gamesys/DebugGraph.h create mode 100644 neo/prey/game/gamesys/Event.cpp create mode 100644 neo/prey/game/gamesys/Event.h create mode 100644 neo/prey/game/gamesys/SaveGame.cpp create mode 100644 neo/prey/game/gamesys/SaveGame.h create mode 100644 neo/prey/game/gamesys/SysCmds.cpp create mode 100644 neo/prey/game/gamesys/SysCmds.h create mode 100644 neo/prey/game/gamesys/SysCvar.cpp create mode 100644 neo/prey/game/gamesys/SysCvar.h create mode 100644 neo/prey/game/physics/Clip.cpp create mode 100644 neo/prey/game/physics/Clip.h create mode 100644 neo/prey/game/physics/Force.cpp create mode 100644 neo/prey/game/physics/Force.h create mode 100644 neo/prey/game/physics/Force_Constant.cpp create mode 100644 neo/prey/game/physics/Force_Constant.h create mode 100644 neo/prey/game/physics/Force_Drag.cpp create mode 100644 neo/prey/game/physics/Force_Drag.h create mode 100644 neo/prey/game/physics/Force_Field.cpp create mode 100644 neo/prey/game/physics/Force_Field.h create mode 100644 neo/prey/game/physics/Force_Spring.cpp create mode 100644 neo/prey/game/physics/Force_Spring.h create mode 100644 neo/prey/game/physics/Physics.cpp create mode 100644 neo/prey/game/physics/Physics.h create mode 100644 neo/prey/game/physics/Physics_AF.cpp create mode 100644 neo/prey/game/physics/Physics_AF.h create mode 100644 neo/prey/game/physics/Physics_Actor.cpp create mode 100644 neo/prey/game/physics/Physics_Actor.h create mode 100644 neo/prey/game/physics/Physics_Base.cpp create mode 100644 neo/prey/game/physics/Physics_Base.h create mode 100644 neo/prey/game/physics/Physics_Monster.cpp create mode 100644 neo/prey/game/physics/Physics_Monster.h create mode 100644 neo/prey/game/physics/Physics_Parametric.cpp create mode 100644 neo/prey/game/physics/Physics_Parametric.h create mode 100644 neo/prey/game/physics/Physics_Player.cpp create mode 100644 neo/prey/game/physics/Physics_Player.h create mode 100644 neo/prey/game/physics/Physics_PreyPlayer.cpp create mode 100644 neo/prey/game/physics/Physics_PreyPlayer.h create mode 100644 neo/prey/game/physics/Physics_RigidBody.cpp create mode 100644 neo/prey/game/physics/Physics_RigidBody.h create mode 100644 neo/prey/game/physics/Physics_Static.cpp create mode 100644 neo/prey/game/physics/Physics_Static.h create mode 100644 neo/prey/game/physics/Physics_StaticMulti.cpp create mode 100644 neo/prey/game/physics/Physics_StaticMulti.h create mode 100644 neo/prey/game/physics/Push.cpp create mode 100644 neo/prey/game/physics/Push.h create mode 100644 neo/prey/game/projectile.cpp create mode 100644 neo/prey/game/projectile.h create mode 100644 neo/prey/game/script/Script_Compiler.cpp create mode 100644 neo/prey/game/script/Script_Compiler.h create mode 100644 neo/prey/game/script/Script_Interpreter.cpp create mode 100644 neo/prey/game/script/Script_Interpreter.h create mode 100644 neo/prey/game/script/Script_Program.cpp create mode 100644 neo/prey/game/script/Script_Program.h create mode 100644 neo/prey/game/script/Script_Thread.cpp create mode 100644 neo/prey/game/script/Script_Thread.h create mode 100644 neo/prey/game/trigger.cpp create mode 100644 neo/prey/game/trigger.h create mode 100644 neo/prey/idLib/MsgQueue.cpp create mode 100644 neo/prey/idLib/MsgQueue.h create mode 100644 neo/prey/idLib/containers/PreyStack.h create mode 100644 neo/prey/idLib/math/prey_interpolate.h create mode 100644 neo/prey/idLib/math/prey_math.cpp create mode 100644 neo/prey/idLib/math/prey_math.h create mode 100644 neo/prey/idLib/preylib.h create mode 100644 neo/sys/win32/rc/res/prey.ico create mode 100644 preybase/pk4s_go_here diff --git a/neo/Doom3.sln b/neo/Doom3.sln index 9754b8fd..9dff0276 100644 --- a/neo/Doom3.sln +++ b/neo/Doom3.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.4.11626.88 stable +VisualStudioVersion = 18.4.11626.88 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{347D107C-D787-4408-A60D-86FA45997F9B}" EndProject @@ -20,95 +20,81 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MayaImport", "MayaImport.vc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "opengl", "opengl\opengl.vcxproj", "{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "preydlls", "preydlls", "{F65E2305-0208-4A25-BEB0-038C92484211}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PreyGame", "prey\PreyGame.vcxproj", "{6CD8E46D-E28E-493F-BB06-537A637A68C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug with inlines and memory log|x64 = Debug with inlines and memory log|x64 - Debug with inlines|x64 = Debug with inlines|x64 Debug|x64 = Debug|x64 - Dedicated Debug with inlines|x64 = Dedicated Debug with inlines|x64 Dedicated Debug|x64 = Dedicated Debug|x64 Dedicated Release|x64 = Dedicated Release|x64 + Prey Debug|x64 = Prey Debug|x64 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64 - {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines and memory log|x64.Build.0 = Debug with inlines and memory log|x64 - {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64 - {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines|x64.Build.0 = Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Debug|x64.ActiveCfg = Debug|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Debug|x64.Build.0 = Debug|x64 - {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64 - {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug with inlines|x64.Build.0 = Dedicated Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug|x64.Build.0 = Dedicated Debug|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Release|x64.Build.0 = Dedicated Release|x64 + {49BEC5C6-B964-417A-851E-808886B57430}.Prey Debug|x64.ActiveCfg = Prey Debug|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Release|x64.ActiveCfg = Release|x64 {49BEC5C6-B964-417A-851E-808886B57430}.Release|x64.Build.0 = Release|x64 - {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64 - {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines and memory log|x64.Build.0 = Debug with inlines and memory log|x64 - {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64 - {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines|x64.Build.0 = Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Debug|x64.ActiveCfg = Debug|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Debug|x64.Build.0 = Debug|x64 - {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64 - {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug with inlines|x64.Build.0 = Dedicated Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug|x64.Build.0 = Dedicated Debug|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Release|x64.Build.0 = Dedicated Release|x64 + {49BEC5C6-B964-417A-851E-808886B57400}.Prey Debug|x64.ActiveCfg = Prey Debug|x64 + {49BEC5C6-B964-417A-851E-808886B57400}.Prey Debug|x64.Build.0 = Prey Debug|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Release|x64.ActiveCfg = Release|x64 {49BEC5C6-B964-417A-851E-808886B57400}.Release|x64.Build.0 = Release|x64 - {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines and memory log|x64.ActiveCfg = Debug|x64 - {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines and memory log|x64.Build.0 = Debug|x64 - {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines|x64.ActiveCfg = Debug|x64 - {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines|x64.Build.0 = Debug|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug|x64.ActiveCfg = Debug|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug|x64.Build.0 = Debug|x64 - {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug with inlines|x64.ActiveCfg = Debug|x64 - {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug with inlines|x64.Build.0 = Debug|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug|x64.ActiveCfg = Debug|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug|x64.Build.0 = Debug|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Release|x64.ActiveCfg = Release|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Release|x64.Build.0 = Release|x64 + {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Prey Debug|x64.ActiveCfg = Prey Debug|x64 + {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Prey Debug|x64.Build.0 = Prey Debug|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Release|x64.ActiveCfg = Release|x64 {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Release|x64.Build.0 = Release|x64 - {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64 - {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines and memory log|x64.Build.0 = Debug with inlines and memory log|x64 - {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64 - {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines|x64.Build.0 = Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Debug|x64.ActiveCfg = Debug|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Debug|x64.Build.0 = Debug|x64 - {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64 - {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug with inlines|x64.Build.0 = Dedicated Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug|x64.Build.0 = Dedicated Debug|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Release|x64.Build.0 = Dedicated Release|x64 + {49BEC5C6-B964-417A-851E-808886B57420}.Prey Debug|x64.ActiveCfg = Prey Debug|x64 + {49BEC5C6-B964-417A-851E-808886B57420}.Prey Debug|x64.Build.0 = Prey Debug|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Release|x64.ActiveCfg = Release|x64 {49BEC5C6-B964-417A-851E-808886B57420}.Release|x64.Build.0 = Release|x64 - {49BEC5C6-B964-417A-851E-808886B574F1}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64 - {49BEC5C6-B964-417A-851E-808886B574F1}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B574F1}.Debug|x64.ActiveCfg = Debug|x64 {49BEC5C6-B964-417A-851E-808886B574F1}.Debug|x64.Build.0 = Debug|x64 - {49BEC5C6-B964-417A-851E-808886B574F1}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64 {49BEC5C6-B964-417A-851E-808886B574F1}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64 {49BEC5C6-B964-417A-851E-808886B574F1}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64 + {49BEC5C6-B964-417A-851E-808886B574F1}.Prey Debug|x64.ActiveCfg = Prey Debug|x64 + {49BEC5C6-B964-417A-851E-808886B574F1}.Prey Debug|x64.Build.0 = Prey Debug|x64 {49BEC5C6-B964-417A-851E-808886B574F1}.Release|x64.ActiveCfg = Release|x64 - {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines and memory log|x64.ActiveCfg = Debug|x64 - {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines and memory log|x64.Build.0 = Debug|x64 - {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines|x64.ActiveCfg = Debug|x64 - {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines|x64.Build.0 = Debug|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug|x64.ActiveCfg = Debug|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug|x64.Build.0 = Debug|x64 - {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug with inlines|x64.ActiveCfg = Debug|x64 - {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug with inlines|x64.Build.0 = Debug|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug|x64.ActiveCfg = Debug|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug|x64.Build.0 = Debug|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Release|x64.ActiveCfg = Release|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Release|x64.Build.0 = Release|x64 + {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Prey Debug|x64.ActiveCfg = Prey Debug|x64 + {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Prey Debug|x64.Build.0 = Prey Debug|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Release|x64.ActiveCfg = Release|x64 {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Release|x64.Build.0 = Release|x64 + {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Debug|x64.ActiveCfg = Debug|x64 + {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug with Inlines|x64 + {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64 + {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Prey Debug|x64.ActiveCfg = Prey Debug|x64 + {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Prey Debug|x64.Build.0 = Prey Debug|x64 + {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Release|x64.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -120,6 +106,7 @@ Global {49BEC5C6-B964-417A-851E-808886B57420} = {003B01AB-152D-45C8-BF45-E5A035042D7F} {49BEC5C6-B964-417A-851E-808886B574F1} = {1E2B3940-65F8-4D8F-9EEE-85E94EBBC6DF} {58320C19-5139-47C5-AAFE-4CAD2C8ED14D} = {003B01AB-152D-45C8-BF45-E5A035042D7F} + {6CD8E46D-E28E-493F-BB06-537A637A68C2} = {F65E2305-0208-4A25-BEB0-038C92484211} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {822AE68E-37FD-4947-AC78-29CFA29E613F} diff --git a/neo/MayaImport.vcxproj b/neo/MayaImport.vcxproj index bd1a02c4..87b5cc6c 100644 --- a/neo/MayaImport.vcxproj +++ b/neo/MayaImport.vcxproj @@ -25,6 +25,10 @@ Dedicated Release x64 + + Prey Debug + x64 + Release x64 @@ -86,6 +90,12 @@ MultiByte v145 + + DynamicLibrary + false + MultiByte + v145 + @@ -138,6 +148,12 @@ + + + + + + <_ProjectFileVersion>10.0.40219.1 @@ -148,8 +164,11 @@ AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset @@ -171,6 +190,14 @@ ../$(TargetName)$(TargetExt) + + + MayaImport/fbxsdk/include;%(AdditionalIncludeDirectories) + + + ../$(TargetName)$(TargetExt) + + MayaImport/fbxsdk/include;%(AdditionalIncludeDirectories) @@ -229,6 +256,7 @@ Create Create Create + Create Create Create Create diff --git a/neo/cm/PreyCM/CollisionModel.h b/neo/cm/PreyCM/CollisionModel.h new file mode 100644 index 00000000..870d67ba --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel.h @@ -0,0 +1,146 @@ +// Copyright (C) 2004 Id Software, Inc. +// + +#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 +typedef enum { + 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 +} contactType_t; + +// contact info +typedef struct { + contactType_t type; // contact type + idVec3 point; // point of contact + idVec3 normal; // contact plane normal + float dist; // contact plane distance + 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 +} contactInfo_t; + +// trace result +typedef struct trace_s { + 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 +} trace_t; + +typedef int cmHandle_t; + +#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 + +//HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS +#define PROC_CLIPMODEL_INDEX_START 1 +#define PROC_CLIPMODEL_STRING_PRFX "inlined_proc_clip_" +#endif +//HUMANHEAD END + +class idCollisionModelManager { +public: + virtual ~idCollisionModelManager( void ) {} + + // Loads collision models from a map file. + virtual void LoadMap( const idMapFile *mapFile ) = 0; + +// HUMANHEAD pdm: Support for level appending + virtual const char * ContentsName(const int contents) const = 0; + const char * StringFromContents( const int contents ) const; +#if DEATHWALK_AUTOLOAD + virtual void AppendMap( const idMapFile *mapFile ) = 0; + virtual bool WillUseAlreadyLoadedCollisionMap( const idMapFile *mapFile) = 0; +#endif +// HUMANHEAD END + + // Frees all the collision models. + virtual void FreeMap( void ) = 0; + + // Gets the clip handle for a model. + virtual cmHandle_t LoadModel( const char *modelName, const bool precache ) = 0; + // Sets up a trace model for collision with other trace models. + virtual cmHandle_t SetupTrmModel( const idTraceModel &trm, const idMaterial *material ) = 0; + // Creates a trace model from a collision model, returns true if succesfull. + virtual bool TrmFromModel( const char *modelName, idTraceModel &trm ) = 0; + + // Gets the name of a model. + virtual const char * GetModelName( cmHandle_t model ) const = 0; + // Gets the bounds of a model. + virtual bool GetModelBounds( cmHandle_t model, idBounds &bounds ) const = 0; + // Gets all contents flags of brushes and polygons of a model ored together. + virtual bool GetModelContents( cmHandle_t model, int &contents ) const = 0; + // Gets a vertex of a model. + virtual bool GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const = 0; + // Gets an edge of a model. + virtual bool GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Gets a polygon of a model. + virtual bool GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const = 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, + cmHandle_t 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, + cmHandle_t 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, + cmHandle_t 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, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + + // Tests collision detection. + virtual void DebugOutput( const idVec3 &origin ) = 0; + // Draws a model. + virtual void DrawModel( cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const float radius ) = 0; + // Prints model information, use -1 handle for accumulated model info. + virtual void ModelInfo( cmHandle_t model ) = 0; + // Lists all loaded models. + virtual void ListModels( 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; + + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + virtual int GetNumInlinedProcClipModels(void) = 0; +#endif + //HUMANHEAD END +}; + +extern idCollisionModelManager * collisionModelManager; + +#endif /* !__COLLISIONMODELMANAGER_H__ */ diff --git a/neo/cm/PreyCM/CollisionModel_contacts.cpp b/neo/cm/PreyCM/CollisionModel_contacts.cpp new file mode 100644 index 00000000..047a879d --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_contacts.cpp @@ -0,0 +1,48 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "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/neo/cm/PreyCM/CollisionModel_contents.cpp b/neo/cm/PreyCM/CollisionModel_contents.cpp new file mode 100644 index 00000000..bffec167 --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_contents.cpp @@ -0,0 +1,614 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "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/neo/cm/PreyCM/CollisionModel_debug.cpp b/neo/cm/PreyCM/CollisionModel_debug.cpp new file mode 100644 index 00000000..025772c8 --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_debug.cpp @@ -0,0 +1,498 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + + +/* +=============================================================================== + +Visualisation code + +=============================================================================== +*/ + +const char *cm_contentsNameByIndex[] = { +// HUMANHEAD: Redid these as they were out of sync + "none", // + "solid", // bit 0 + "opaque", // 1 + "water", // 2 + "playerclip", // 3 + "monsterclip", // 4 + "moveableclip", // 5 + "ikclip", // 6 + "blood", // 7 + "body", // 8 + "projectile", // 9 + "corpse", // 10 + "rendermodel", // 11 + "trigger", // 12 + "aas_solid", // 13 + "aas_obstacle", // 14 + "flashlight_trigger", // 15 + + // HUMANHEAD pdm: added our contents + "forcefield", // 16 + "spiritbridge", // 17 + "areaportal", // 18 + "nocsg", // 19 + "block_radiusdamage", // 20 + "shootable", // 21 + "deathvolume", // 22 + "vehicleclip", // 23 + "owner_to_owner", // 24 + "game_portal", // 25 + "shootablebyarrow", // 26 + "hunterclip", // 27 + + // END HUMANHEAD + NULL +}; + +int cm_contentsFlagByIndex[] = { +// HUMANHEAD: Redid these as they were out of sync + -1, // -1 + CONTENTS_SOLID, // bit 0 + CONTENTS_OPAQUE, // 1 + CONTENTS_WATER, // 2 + CONTENTS_PLAYERCLIP, // 3 + CONTENTS_MONSTERCLIP, // 4 + CONTENTS_MOVEABLECLIP, // 5 + CONTENTS_IKCLIP, // 6 + CONTENTS_BLOOD, // 7 + CONTENTS_BODY, // 8 + CONTENTS_PROJECTILE, // 9 + CONTENTS_CORPSE, // 10 + CONTENTS_RENDERMODEL, // 11 + CONTENTS_TRIGGER, // 12 + CONTENTS_AAS_SOLID, // 13 + CONTENTS_AAS_OBSTACLE, // 14 + CONTENTS_FLASHLIGHT_TRIGGER, // 15 + + // HUMANHEAD pdm: added our contents + CONTENTS_FORCEFIELD, // 16 + CONTENTS_SPIRITBRIDGE, // 17 + CONTENTS_AREAPORTAL, // 18 + CONTENTS_NOCSG, // 19 + CONTENTS_BLOCK_RADIUSDAMAGE, // 20 + CONTENTS_SHOOTABLE, // 21 + CONTENTS_DEATHVOLUME, // 22 + CONTENTS_VEHICLECLIP, // 23 + CONTENTS_OWNER_TO_OWNER, // 24 + CONTENTS_GAME_PORTAL, // 25 + CONTENTS_SHOOTABLEBYARROW, // 26 + CONTENTS_HUNTERCLIP, // 27 + // END HUMANHEAD + 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; + +/* +================ +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; +} diff --git a/neo/cm/PreyCM/CollisionModel_files.cpp b/neo/cm/PreyCM/CollisionModel_files.cpp new file mode 100644 index 00000000..5950ab39 --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_files.cpp @@ -0,0 +1,598 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +#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; + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + if (anyInlinedProcClipMats) { + if (token.Cmpn(PROC_CLIPMODEL_STRING_PRFX, strlen(PROC_CLIPMODEL_STRING_PRFX)) == 0) { + numInlinedProcClipModels++; + } + } +#endif + //HUMANHEAD END + 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; +} diff --git a/neo/cm/PreyCM/CollisionModel_load.cpp b/neo/cm/PreyCM/CollisionModel_load.cpp new file mode 100644 index 00000000..ef8f1003 --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_load.cpp @@ -0,0 +1,3955 @@ + +/* +=============================================================================== + + 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 "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( "}" ); +} + +//HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS +typedef struct basicSurf_s { + idDrawVert *verts; + int numVerts; + glIndex_t *indices; + int numIndices; + const idMaterial *mat; +} basicSurf_t; +/* +================ +idCollisionModelManagerLocal::CheckProcModelSurfClip +================ +*/ +void idCollisionModelManagerLocal::CheckProcModelSurfClip(idLexer *src) { + idToken token; + int i, j; + idList basicSurfs; + bool parseOver = false; + + src->ExpectTokenString( "{" ); + + // parse the name + //unfortunately, this isn't designer-recognizable and tends to bunch things up, so can't be used instead of material name + //however, we can expect only _area* models to contain inlined geometry + src->ExpectAnyToken( &token ); + if (token.Cmpn("_area", strlen("_area")) != 0) { + parseOver = true; + } + + int numSurfaces = src->ParseInt(); + if ( numSurfaces < 0 ) { + src->Error( "idCollisionModelManagerLocal::CheckProcModelSurfClip: bad numSurfaces" ); + } + + cm_node_t *node; + cm_model_t *model = NULL; + idBounds totalBounds; + + totalBounds.Zero(); + + for ( i = 0 ; i < numSurfaces ; i++ ) { + src->ExpectTokenString( "{" ); + + src->ExpectAnyToken( &token ); + + //check if this material is in the material list + if (!parseOver) { + for (j = 0; j < inlinedProcClipModelMats.Num(); j++) { + if (inlinedProcClipModelMats[j] == token) { + break; + } + } + } + if (parseOver || j == inlinedProcClipModelMats.Num()) { //just parse over this surf + int nV, nI; + nV = src->ParseInt(); + nI = src->ParseInt(); + for ( j = 0 ; j < nV ; j++ ) { + float vec[8]; + src->Parse1DMatrix( 8, vec ); + } + for ( j = 0 ; j < nI ; j++ ) { + src->ParseInt(); + } + src->ExpectTokenString( "}" ); + continue; + } + + const idMaterial *mat = declManager->FindMaterial( token ); + + //we have a surface we want to use + if (!model) { + model = AllocModel(); + + model->name = va("%s%i", PROC_CLIPMODEL_STRING_PRFX, numInlinedProcClipModels); + 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; + } + + int numVerts = src->ParseInt(); + int numIndices = src->ParseInt(); + + model->maxVertices += numVerts; + model->maxEdges += numIndices; + + idDrawVert *verts = (idDrawVert *)Mem_Alloc(numVerts*sizeof(idDrawVert)); + glIndex_t *indices = (glIndex_t *)Mem_Alloc(numIndices*sizeof(glIndex_t)); + + //parse the actual verts and tris + for ( j = 0 ; j < numVerts ; j++ ) { + float vec[8]; + + src->Parse1DMatrix( 8, vec ); + + verts[j].xyz[0] = vec[0]; + verts[j].xyz[1] = vec[1]; + verts[j].xyz[2] = vec[2]; + verts[j].st[0] = vec[3]; + verts[j].st[1] = vec[4]; + verts[j].normal[0] = vec[5]; + verts[j].normal[1] = vec[6]; + verts[j].normal[2] = vec[7]; + } + + for ( j = 0 ; j < numIndices ; j++ ) { + indices[j] = src->ParseInt(); + } + src->ExpectTokenString( "}" ); + + //calculate a bounds for the surface + idBounds surfBounds; + surfBounds.Zero(); + SIMDProcessor->MinMax(surfBounds[0], surfBounds[1], verts, numVerts); + totalBounds.AddBounds(surfBounds); + + basicSurf_t bs; + bs.verts = verts; + bs.numVerts = numVerts; + bs.indices = indices; + bs.numIndices = numIndices; + bs.mat = mat; + basicSurfs.Append(bs); + } + + src->ExpectTokenString( "}" ); + + if (model) { //we put together a model from the proc surfs + idFixedWinding w; + idPlane plane; + + assert(basicSurfs.Num() > 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) ); + + assert(cm_vertexHash && cm_edgeHash); //after all, this should only be called while building models + cm_vertexHash->ResizeIndex( model->maxVertices ); + cm_edgeHash->ResizeIndex( model->maxEdges ); + ClearHash(totalBounds); + + for (i = 0; i < basicSurfs.Num(); i++) { + for ( j = 0; j < basicSurfs[i].numIndices; j += 3 ) { + w.Clear(); + w += basicSurfs[i].verts[ basicSurfs[i].indices[ j + 2 ] ].xyz; + w += basicSurfs[i].verts[ basicSurfs[i].indices[ j + 1 ] ].xyz; + w += basicSurfs[i].verts[ basicSurfs[i].indices[ j + 0 ] ].xyz; + w.GetPlane( plane ); + plane = -plane; + PolygonFromWinding( model, &w, plane, basicSurfs[i].mat, 1 ); + } + //free up the temp proc surf memory now that we're done with it + Mem_Free(basicSurfs[i].verts); + Mem_Free(basicSurfs[i].indices); + } + + // create a BSP tree for the model + model->node = CreateAxialBSPTree( model, model->node ); + + model->isConvex = false; + + FinishModel( model ); + + //add our new clipmodel to the list + models[PROC_CLIPMODEL_INDEX_START+numInlinedProcClipModels] = model; + numInlinedProcClipModels++; + } +} +#endif +//HUMANHEAD END + +/* +================ +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" ) { + //HUMANHEAD rww - parse model surfs to create clip models +#if _HH_INLINED_PROC_CLIPMODELS + if (inlinedProcClipModelMats.Num() > 0) { + CheckProcModelSurfClip(src); + } + //HUMANHEAD END + else { + src->SkipBracedSection(); + } +#else + src->SkipBracedSection(); +#endif + 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; + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + inlinedProcClipModelMats.Clear(); + numInlinedProcClipModels = 0; + anyInlinedProcClipMats = false; +#endif + //HUMANHEAD END +} + +/* +================ +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->material = trmMaterial; //HUMANHEAD rww + 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; + } + //HUMANHEAD PCF rww 05/11/06 - can be used explicitly by surfaces which use alpha coverage but do not want collision anyway + if ( material->TestMaterialFlag(MF_SKIPCLIP) ) { + return; + } + //HUMANHEAD END + + // 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; + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + const char *clipMat; + anyInlinedProcClipMats = false; + mapEnt = mapFile->GetEntity(0); + if (mapEnt) { + mapEnt->epairs.GetString("clipStaticMaterials1", "", &clipMat); + if (clipMat[0]) { + anyInlinedProcClipMats = true; + } + } +#endif + //HUMANHEAD END + + idTimer timer; + timer.Start(); + + if ( !LoadCollisionModelFile( mapFile->GetName(), mapFile->GetGeometryCRC() ) ) { + +// HUMANHEAD pdm: Support for level appending +#if DEATHWALK_AUTOLOAD + // Keep track of the first model added + // (there may already be collision models from the regular map loaded if this is a deathwalk map) + int firstModel = idCollisionModelManagerLocal::numModels; +#endif +// HUMANHEAD END + + if ( !mapFile->GetNumEntities() ) { + return; + } + + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + if (anyInlinedProcClipMats) { + assert(numInlinedProcClipModels == 0 && inlinedProcClipModelMats.Num() == 0); + numInlinedProcClipModels = 0; + mapEnt = mapFile->GetEntity(0); + inlinedProcClipModelMats.Clear(); + if (mapEnt) { + for (i = 0; 1; i++) { + mapEnt->epairs.GetString(va("clipStaticMaterials%i", i+1), "", &clipMat); + if (clipMat[0]) { //a list of materials specifying proc surfaces to be turned into clip + inlinedProcClipModelMats.Append(clipMat); + } + else { + break; + } + } + } + } +#endif + //HUMANHEAD END + + // 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++; + } + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + if (numInlinedProcClipModels && numModels == PROC_CLIPMODEL_INDEX_START) { + numModels += numInlinedProcClipModels; + } +#endif + //HUMANHEAD END + } + + // free the proc bsp which is only used for data optimization + Mem_Free( procNodes ); + procNodes = NULL; + + // write the collision models to a file +// HUMANHEAD pdm: Support for level appending +#if DEATHWALK_AUTOLOAD + bool bAppending = true; // jmarshall session->ShouldAppendLevel() && !idStr::Icmp(mapFile->GetName(), session->GetDeathwalkMapName()); + if (bAppending) { + common->Printf("Appending collision data\n"); + // Rename the world collision model + if (models[firstModel]) { + models[firstModel]->name = "dw_worldMap"; + } + } + WriteCollisionModelsToFile( mapFile->GetName(), firstModel, numModels, mapFile->GetGeometryCRC() ); +#else + WriteCollisionModelsToFile( mapFile->GetName(), 0, numModels, mapFile->GetGeometryCRC() ); +#endif + } + + 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 ); +} + +// HUMANHEAD pdm: Support for level appending +#if DEATHWALK_AUTOLOAD +bool idCollisionModelManagerLocal::WillUseAlreadyLoadedCollisionMap(const idMapFile *mapFile) { + // Based on some code from idCollisionModelManagerLocal::LoadMap + return (idCollisionModelManagerLocal::loaded && + idCollisionModelManagerLocal::mapName.Icmp( mapFile->GetName() ) == 0 && + mapFile->GetFileTime() == idCollisionModelManagerLocal::mapFileTime); +} +void idCollisionModelManagerLocal::AppendMap( const idMapFile *mapFile ) { + // build collision models for our appended map + if (mapFile) { + idCollisionModelManagerLocal::BuildModels( mapFile ); + } +} +#endif +// HUMANHEAD END + +//HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS +int idCollisionModelManagerLocal::GetNumInlinedProcClipModels(void) { + return numInlinedProcClipModels; +} +#endif +//HUMANHEAD END diff --git a/neo/cm/PreyCM/CollisionModel_local.h b/neo/cm/PreyCM/CollisionModel_local.h new file mode 100644 index 00000000..f9bc0b5b --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_local.h @@ -0,0 +1,527 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "CollisionModel.h" + +#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 { + idVec3 p; // vertex point + int checkcount; // for multi-check avoidance + 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 + int checkcount; // for multi-check avoidance + int contents; // contents behind polygon + const idMaterial * material; // material + idPlane plane; // polygon plane + int numEdges; // number of edges + int edges[1]; // variable sized, indexes into cm_edge_t list +} 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 { + int checkcount; // for multi-check avoidance + idBounds bounds; // brush bounds + int contents; // contents of brush + const idMaterial * material; // material + int primitiveNum; // number of brush primitive + int numPlanes; // number of bounding planes + idPlane planes[1]; // variable sized +} 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; + +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; + +/* +=============================================================================== + +Data used during collision detection calculations + +=============================================================================== +*/ + +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; + +/* +=============================================================================== + +Collision Map + +=============================================================================== +*/ + +typedef struct cm_procNode_s { + idPlane plane; + int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid +} cm_procNode_t; + +class idCollisionModelManagerLocal : public idCollisionModelManager { +public: + // load collision models from a map file + void LoadMap( const idMapFile *mapFile ); + +// HUMANHEAD pdm: Support for level appending + virtual const char * ContentsName(const int contents) const { return StringFromContents(contents); } +#if DEATHWALK_AUTOLOAD + virtual void AppendMap( const idMapFile *mapFile ); + virtual bool WillUseAlreadyLoadedCollisionMap(const idMapFile *mapFile); +#endif +// HUMANHEAD END + + // 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 ); + + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + int GetNumInlinedProcClipModels(void); +#endif + //HUMANHEAD END + +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 ); + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + void CheckProcModelSurfClip(idLexer *src); +#endif + //HUMANHEAD END + 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; + unsigned int 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; + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + idList inlinedProcClipModelMats; + int numInlinedProcClipModels; + bool anyInlinedProcClipMats; +#endif + //HUMANHEAD END +}; + +// for debugging +extern idCVar cm_debugCollision; diff --git a/neo/cm/PreyCM/CollisionModel_rotate.cpp b/neo/cm/PreyCM/CollisionModel_rotate.cpp new file mode 100644 index 00000000..f8115890 --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_rotate.cpp @@ -0,0 +1,1672 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +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; + if (q == 0.0f) { //HUMANHEAD rww - CUFPF + frac2 = 0.0f; + } + else { + 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 fucked 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 +} diff --git a/neo/cm/PreyCM/CollisionModel_trace.cpp b/neo/cm/PreyCM/CollisionModel_trace.cpp new file mode 100644 index 00000000..1471f97a --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_trace.cpp @@ -0,0 +1,229 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "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/neo/cm/PreyCM/CollisionModel_translate.cpp b/neo/cm/PreyCM/CollisionModel_translate.cpp new file mode 100644 index 00000000..b0d5a134 --- /dev/null +++ b/neo/cm/PreyCM/CollisionModel_translate.cpp @@ -0,0 +1,1100 @@ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "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; + if (d1 == d2) { //HUMANHEAD rww - CUFPF + f1 = 0.0f; + } + else { + 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 fucked 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.trace.c.id = 0; // HUMANHEAD pdm: initialize so we don't get bogus values back + 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" ); + common->Printf( " of entity: %s\n", tw.model->name.c_str() ); // HUMANHEAD + 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/neo/curllib.vcxproj b/neo/curllib.vcxproj index 7762ffbd..7276455e 100644 --- a/neo/curllib.vcxproj +++ b/neo/curllib.vcxproj @@ -5,6 +5,10 @@ Debug x64 + + Prey Debug + x64 + Release x64 @@ -33,6 +37,11 @@ MultiByte v145 + + StaticLibrary + MultiByte + v145 + @@ -48,12 +57,21 @@ + + + + + + <_ProjectFileVersion>10.0.40219.1 AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset @@ -64,6 +82,12 @@ _USRDLL;CURLLIB_EXPORTS;%(PreprocessorDefinitions) + + + .\curl\include;%(AdditionalIncludeDirectories) + _USRDLL;CURLLIB_EXPORTS;%(PreprocessorDefinitions) + + .\curl\include;%(AdditionalIncludeDirectories) diff --git a/neo/doomdll.vcxproj b/neo/doomdll.vcxproj index 26e18ec1..62f7b68a 100644 --- a/neo/doomdll.vcxproj +++ b/neo/doomdll.vcxproj @@ -25,6 +25,10 @@ Dedicated Release x64 + + Prey Debug + x64 + Release x64 @@ -88,6 +92,12 @@ MultiByte v145 + + Application + false + MultiByte + v145 + @@ -140,6 +150,12 @@ + + + + + + <_ProjectFileVersion>10.0.40219.1 @@ -150,8 +166,11 @@ AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset @@ -204,6 +223,20 @@ precompiled.h + + + openal\lib;sys\win32\dongle;%(AdditionalLibraryDirectories) + ../Prey.exe + d3d12.lib;nafxcwd.lib;libcmtd.lib;%(AdditionalDependencies) + + + Use + + + precompiled.h + PREY;_DEBUG;%(PreprocessorDefinitions) + + openal\lib;sys\win32\dongle;%(AdditionalLibraryDirectories) @@ -260,6 +293,8 @@ + + @@ -637,6 +672,7 @@ true true true + true true true true @@ -646,6 +682,7 @@ true true true + true true true true @@ -655,6 +692,7 @@ true true true + true true true true @@ -664,6 +702,7 @@ true true true + true true true true @@ -673,6 +712,7 @@ true true true + true true true true @@ -682,6 +722,7 @@ true true true + true true true true @@ -691,6 +732,7 @@ true true true + true true true true @@ -700,6 +742,7 @@ true true true + true true true true @@ -709,6 +752,7 @@ true true true + true true true true @@ -1261,14 +1305,94 @@ - - - - - - - - + + false + false + false + false + false + true + false + false + + + false + false + false + false + false + true + false + false + + + false + false + false + false + false + true + false + false + + + false + false + false + false + false + true + false + false + + + false + false + false + false + false + true + false + false + + + false + false + false + false + false + true + false + false + + + false + false + false + false + false + true + false + false + + + false + false + false + false + false + true + false + false + + + + + + + + + @@ -1300,6 +1424,7 @@ Create + Create Create Create Create @@ -1355,6 +1480,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1364,6 +1490,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1373,6 +1500,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1382,6 +1510,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1391,6 +1520,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1400,6 +1530,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1409,6 +1540,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1418,6 +1550,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1427,6 +1560,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1436,6 +1570,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1445,6 +1580,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1454,6 +1590,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1463,6 +1600,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1472,6 +1610,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1481,6 +1620,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1490,6 +1630,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1499,6 +1640,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1508,6 +1650,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1517,6 +1660,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1526,6 +1670,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1535,6 +1680,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1544,6 +1690,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1553,6 +1700,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1562,6 +1710,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1571,6 +1720,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1580,6 +1730,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1589,6 +1740,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1598,6 +1750,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1607,6 +1760,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1616,6 +1770,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1625,6 +1780,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1634,6 +1790,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1643,6 +1800,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1652,6 +1810,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1661,6 +1820,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1670,6 +1830,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1679,6 +1840,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1688,6 +1850,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1697,6 +1860,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1706,6 +1870,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1715,6 +1880,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1724,6 +1890,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1733,6 +1900,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1742,6 +1910,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1759,6 +1928,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1768,6 +1938,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1777,6 +1948,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1786,6 +1958,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1795,6 +1968,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1804,6 +1978,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1813,6 +1988,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1822,6 +1998,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1831,6 +2008,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1840,6 +2018,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1849,6 +2028,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1858,6 +2038,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1867,6 +2048,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1876,6 +2058,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1885,6 +2068,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1894,6 +2078,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1903,6 +2088,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1912,6 +2098,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1921,6 +2108,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1930,6 +2118,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1939,6 +2128,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1948,6 +2138,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1957,6 +2148,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1966,6 +2158,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1975,6 +2168,7 @@ NotUsing + NotUsing NotUsing NotUsing NotUsing @@ -1991,6 +2185,7 @@ C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories) + C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories) @@ -2247,6 +2442,7 @@ true true true + true true true true @@ -2256,6 +2452,7 @@ true true true + true true true true @@ -2265,6 +2462,7 @@ true true true + true true true true @@ -2274,6 +2472,7 @@ true true true + true true true true @@ -2283,6 +2482,7 @@ true true true + true true true true @@ -2292,6 +2492,7 @@ true true true + true true true true @@ -2301,6 +2502,7 @@ true true true + true true true true @@ -2310,6 +2512,7 @@ true true true + true true true true @@ -2319,6 +2522,7 @@ true true true + true true true true @@ -2998,6 +3202,7 @@ true true true + true true true true @@ -3007,6 +3212,7 @@ true true true + true true true true @@ -3016,6 +3222,7 @@ true true true + true true true true @@ -3025,6 +3232,7 @@ true true true + true true true true @@ -3035,6 +3243,7 @@ true true true + true true true true @@ -3044,6 +3253,7 @@ true true true + true true true true @@ -3053,6 +3263,7 @@ true true true + true true true true @@ -3062,6 +3273,7 @@ true true true + true true true true @@ -3071,6 +3283,7 @@ true true true + true true true true @@ -3080,6 +3293,7 @@ true true true + true true true true @@ -3089,6 +3303,7 @@ true true true + true true true true @@ -3098,6 +3313,7 @@ true true true + true true true true @@ -3119,6 +3335,12 @@ {58320c19-5139-47c5-aafe-4cad2c8ed14d} + + {6cd8e46d-e28e-493f-bb06-537a637a68c2} + + + + diff --git a/neo/doomdll.vcxproj.filters b/neo/doomdll.vcxproj.filters index b1e622c3..7fb6cf83 100644 --- a/neo/doomdll.vcxproj.filters +++ b/neo/doomdll.vcxproj.filters @@ -106,6 +106,9 @@ {b3166d25-0562-49da-bc8d-961006fe1785} + + {f7ebd0db-2189-45da-a492-76ab782e337a} + @@ -603,6 +606,12 @@ Renderer + + CM\PreyCM + + + CM\PreyCM + @@ -1766,6 +1775,30 @@ Renderer + + CM\PreyCM + + + CM\PreyCM + + + CM\PreyCM + + + CM\PreyCM + + + CM\PreyCM + + + CM\PreyCM + + + CM\PreyCM + + + CM\PreyCM + @@ -2484,4 +2517,9 @@ Tools\Script + + + Sys\RC\res + + \ No newline at end of file diff --git a/neo/doomdll.vcxproj.user b/neo/doomdll.vcxproj.user index 785d20f2..609e7c7b 100644 --- a/neo/doomdll.vcxproj.user +++ b/neo/doomdll.vcxproj.user @@ -19,6 +19,13 @@ D:\projects\Doom3 +set r_fullscreen 0 +set r_mode 6|+set r_fullscreen 1 +set r_mode 6|+set r_fullscreen 1 +set r_mode 1|+set r_fullscreen 0 +set r_mode 1| + + D:\projects\Doom3\Prey.exe + WindowsLocalDebugger + +set r_fullscreen 0 +set r_mode 1 + D:\projects\Doom3 + +set r_fullscreen 0 +set r_mode 6|+set r_fullscreen 1 +set r_mode 6|+set r_fullscreen 1 +set r_mode 1|+set r_fullscreen 0 +set r_mode 1| + D:\projects\Doom3\Doom3.exe WindowsLocalDebugger @@ -36,6 +43,7 @@ WindowsLocalDebugger +set r_fullscreen 0 +set r_mode 6 D:\projects\Doom3 + +set r_fullscreen 0 +set r_mode 6| D:\projects\Doom3\Doom3.exe diff --git a/neo/framework/BuildDefines.h b/neo/framework/BuildDefines.h index 11f9c7c0..96c94521 100644 --- a/neo/framework/BuildDefines.h +++ b/neo/framework/BuildDefines.h @@ -133,3 +133,8 @@ If you have questions concerning this license or the applicable additional terms # endif #endif +// jmarshall - have to make this public outside of preygame for the collision model code. +#ifdef PREY +#define DEATHWALK_AUTOLOAD 1 // Append deathwalk map to all maps loaded +#endif +// jmarshall end \ No newline at end of file diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index e16179a5..869f2027 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -146,7 +146,9 @@ public: 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 const idLangDict * GetLanguageDict( void ); - +#ifdef PREY + virtual void MaterialKeyForBinding(const char* binding, char* materialName, char* keyText, bool* wideKey); +#endif virtual const char * KeysFromBinding( const char *bind ); virtual const char * BindingFromKey( const char *key ); @@ -2645,8 +2647,11 @@ void idCommonLocal::LoadGameDLL( void ) { gameImport_t gameImport; gameExport_t gameExport; GetGameAPI_t GetGameAPI; - +#ifdef PREY + fileSystem->FindDLL( "preygame", dllPath, true); +#else fileSystem->FindDLL( "game", dllPath, true ); +#endif if ( !dllPath[ 0 ] ) { common->FatalError( "couldn't find game dynamic library" ); @@ -3122,4 +3127,145 @@ int64_t idCommonLocal::Get_com_engineHz_numerator(void) { int64_t idCommonLocal::Get_com_engineHz_denominator(void) { return com_engineHz_denominator; -} \ No newline at end of file +} + +#ifdef PREY +void idCommonLocal::MaterialKeyForBinding(const char* binding, char* materialName, char* keyText, bool* wideKey) { + bool unused1 = false; + bool unused2 = false; + + idStr keyName; + idStr displayName; + + const char* rawKey = idKeyInput::SingleKeyFromBinding(binding, unused1); + if (rawKey != NULL) { + keyName = rawKey; + } + + const char* localizedKey = idKeyInput::SingleKeyFromBinding(binding, unused2); + if (localizedKey != NULL) { + displayName = localizedKey; + } + + keyText[0] = '\0'; + *wideKey = false; + + if (keyName.Length() <= 0) { + strcpy(materialName, "textures/interface/tips/keywide"); + + const idLangDict* lang = GetLanguageDict(); + strcpy(keyText, lang->GetString("#str_07133")); + + *wideKey = true; + return; + } + + if (!keyName.Icmp("mouse1")) { + strcpy(materialName, "textures/interface/tips/mouse1"); + return; + } + + if (!keyName.Icmp("mouse2")) { + strcpy(materialName, "textures/interface/tips/mouse2"); + return; + } + + if (!keyName.Icmp("mouse3")) { + strcpy(materialName, "textures/interface/tips/mouse3"); + return; + } + + if (!keyName.Icmp("mwheelup")) { + strcpy(materialName, "textures/interface/tips/mouseup"); + return; + } + + if (!keyName.Icmp("mwheeldown")) { + strcpy(materialName, "textures/interface/tips/mousedn"); + return; + } + + if (!keyName.Icmp("uparrow")) { + strcpy(materialName, "textures/interface/tips/uparrow"); + return; + } + + if (!keyName.Icmp("downarrow")) { + strcpy(materialName, "textures/interface/tips/downarrow"); + return; + } + + if (!keyName.Icmp("leftarrow")) { + strcpy(materialName, "textures/interface/tips/leftarrow"); + return; + } + + if (!keyName.Icmp("rightarrow")) { + strcpy(materialName, "textures/interface/tips/rightarrow"); + return; + } + + if (!keyName.Icmp("enter")) { + strcpy(materialName, "textures/interface/tips/enter"); + *wideKey = true; + return; + } + + if (!keyName.Icmp("backspace")) { + strcpy(materialName, "textures/interface/tips/backspace"); + *wideKey = true; + return; + } + + if (!keyName.Icmp("tab")) { + strcpy(materialName, "textures/interface/tips/tab"); + *wideKey = true; + return; + } + + if (!keyName.Icmp("menu")) { + strcpy(materialName, "textures/interface/tips/menu"); + return; + } + + if (!keyName.Icmp("shift")) { + strcpy(materialName, "textures/interface/tips/shift"); + *wideKey = true; + return; + } + + /* + Generic short printable keys use the normal key material and put the + localized/display key text on top of it. + + The original excludes large/special keys from this path: + printscreen, keypad keys, pause, capslock, windows keys, + modifiers, and space. + */ + if (keyName.Icmp("printscreen") && + keyName.Icmpn("kp_", 3) && + keyName.Icmp("pause") && + keyName.Icmp("capslock") && + keyName.Icmp("lwin") && + keyName.Icmp("rwin") && + keyName.Icmp("shift") && + keyName.Icmp("ctrl") && + keyName.Icmp("alt") && + keyName.Icmp("space") && + displayName.Length() <= 3) { + + strcpy(materialName, "textures/interface/tips/key"); + strcpy(keyText, displayName.c_str()); + return; + } + + /* + Everything else falls back to the wide key material with text. + This handles longer key names or special keys that do not have + dedicated art. + */ + strcpy(materialName, "textures/interface/tips/keywide"); + strcpy(keyText, displayName.c_str()); + *wideKey = true; +} +#endif \ No newline at end of file diff --git a/neo/framework/Common.h b/neo/framework/Common.h index 5941dd67..9f2483cd 100644 --- a/neo/framework/Common.h +++ b/neo/framework/Common.h @@ -37,6 +37,10 @@ If you have questions concerning this license or the applicable additional terms ============================================================== */ +#ifdef PREY +class idKeyInput; +#endif + typedef enum { EDITOR_NONE = 0, EDITOR_RADIANT = BIT(1), @@ -205,6 +209,10 @@ public: // Directly sample a keystate. virtual int KeyState( int key ) = 0; +#ifdef PREY + virtual void MaterialKeyForBinding(const char* binding, char* materialName, char* keyText, bool* wideKey) = 0; +#endif + virtual float Get_com_engineHz_latched(void) = 0; virtual int64_t Get_com_engineHz_numerator(void) = 0; virtual int64_t Get_com_engineHz_denominator(void) = 0; diff --git a/neo/framework/DeclFX.cpp b/neo/framework/DeclFX.cpp index 8f8712f6..e80e3a85 100644 --- a/neo/framework/DeclFX.cpp +++ b/neo/framework/DeclFX.cpp @@ -189,6 +189,16 @@ void idDeclFX::ParseSingleFXAction( idLexer &src, idFXSingleAction& FXAction ) { continue; } +#ifdef PREY + if (!token.Icmp("useAxis")) { + if (!src.ReadToken(&token)) { + break; + } + ParseUseAxis(token, FXAction); + continue; + } +#endif + if ( !token.Icmp( "rotate" ) ) { FXAction.rotate = src.ParseFloat(); continue; @@ -471,3 +481,23 @@ idDeclFX::FreeData void idDeclFX::FreeData( void ) { events.Clear(); } + +#ifdef PREY +void idDeclFX::ParseUseAxis(idStr& text, idFXSingleAction& action) const { + if (!idStr::Icmp(text, "normal")) { + action.useAxis = AXIS_NORMAL; + } + else if (!idStr::Icmp(text, "bounce")) { + action.useAxis = AXIS_BOUNCE; + } + else if (!idStr::Icmp(text, "incoming")) { + action.useAxis = AXIS_INCOMING; + } + else if (!idStr::Icmp(text, "customlocal")) { + action.useAxis = AXIS_CUSTOMLOCAL; + } + else { + action.useAxis = AXIS_CURRENT; + } +} +#endif \ No newline at end of file diff --git a/neo/framework/DeclFX.h b/neo/framework/DeclFX.h index 076eb3f4..db6f91ca 100644 --- a/neo/framework/DeclFX.h +++ b/neo/framework/DeclFX.h @@ -60,7 +60,12 @@ typedef struct { idStr data; idStr name; idStr fire; - +#ifdef PREY + // HUMANHEAD nla + int useAxis; + idVec3 dir; + // HUMANHEAD END +#endif float delay; float duration; float restart; @@ -91,6 +96,11 @@ typedef struct { bool trackOrigin; } idFXSingleAction; + +#ifdef PREY +enum fxAxis { AXIS_CURRENT, AXIS_NORMAL, AXIS_BOUNCE, AXIS_INCOMING, AXIS_CUSTOMLOCAL }; +#endif + // // grouped fx structures // @@ -108,6 +118,12 @@ public: private: void ParseSingleFXAction( idLexer &src, idFXSingleAction& FXAction ); +#ifdef PREY + // HUMANHEAD nla + void ParseUseAxis(idStr& text, idFXSingleAction& action) const; + // HUMANHEAD END +#endif }; + #endif /* !__DECLFX_H__ */ diff --git a/neo/framework/DeclManager.h b/neo/framework/DeclManager.h index 25dac07f..ccce8a51 100644 --- a/neo/framework/DeclManager.h +++ b/neo/framework/DeclManager.h @@ -78,6 +78,9 @@ typedef enum { DECL_EMAIL, DECL_MODELEXPORT, DECL_MAPDEF, +#ifdef PREY + DECL_BEAM, +#endif // new decl types can be added here diff --git a/neo/framework/DeclSkin.h b/neo/framework/DeclSkin.h index deba5823..59a82272 100644 --- a/neo/framework/DeclSkin.h +++ b/neo/framework/DeclSkin.h @@ -50,7 +50,7 @@ public: virtual bool Parse( const char *text, const int textLength ); virtual void FreeData( void ); - const idMaterial * RemapShaderBySkin( const idMaterial *shader ) const; + virtual const idMaterial * RemapShaderBySkin( const idMaterial *shader ) const; // model associations are just for the preview dialog in the editor const int GetNumModelAssociations() const; diff --git a/neo/framework/DeclTable.h b/neo/framework/DeclTable.h index 6097920e..50d130a4 100644 --- a/neo/framework/DeclTable.h +++ b/neo/framework/DeclTable.h @@ -45,7 +45,7 @@ public: virtual bool Parse( const char *text, const int textLength ); virtual void FreeData( void ); - float TableLookup( float index ) const; + virtual float TableLookup( float index ) const; private: bool clamp; diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp index 2a8279fb..f7ae2e3d 100644 --- a/neo/framework/KeyInput.cpp +++ b/neo/framework/KeyInput.cpp @@ -454,6 +454,39 @@ int idKeyInput::GetUsercmdAction( int keynum ) { return keys[ keynum ].usercmdAction; } +#ifdef PREY +/* +=================== +idKeyInput::SingleKeyFromBinding +=================== +*/ +const char* idKeyInput::SingleKeyFromBinding(const char* binding, bool localized) { + static idStr keyName; + + keyName.Clear(); + + if (binding == NULL || binding[0] == '\0') { + return keyName.c_str(); + } + + int keyNum; + + for (keyNum = 0; keyNum < 256; keyNum++) { + if (!idStr::Icmp(keys[keyNum].binding, binding)) { + break; + } + } + + if (keyNum == 256) { + return keyName.c_str(); + } + + keyName.Append(KeyNumToString(keyNum, localized)); + + return keyName.c_str(); +} +#endif + /* =================== Key_Unbind_f diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h index e8b278c9..01b93ae7 100644 --- a/neo/framework/KeyInput.h +++ b/neo/framework/KeyInput.h @@ -207,6 +207,10 @@ public: static int StringToKeyNum( const char *str ); static const char * KeyNumToString( int keyNum, bool localized ); +#ifdef PREY + static const char* SingleKeyFromBinding(const char* binding, bool localized); +#endif + static void SetBinding( int keyNum, const char *binding ); static const char * GetBinding( int keyNum ); static bool UnbindBinding( const char *bind ); diff --git a/neo/framework/Licensee.h b/neo/framework/Licensee.h index cbbd7461..8bc1ddb1 100644 --- a/neo/framework/Licensee.h +++ b/neo/framework/Licensee.h @@ -34,21 +34,34 @@ If you have questions concerning this license or the applicable additional terms =============================================================================== */ -#define GAME_NAME "DOOM 3" // appears on window titles and errors +#ifdef PREY +#define GAME_NAME "PREY" // appears on window titles and errors +#else +#define GAME_NAME "DOOM 3" // appears on window titles and errors +#endif -#define ENGINE_VERSION "DOOM 1.3.1" // printed in console +#define ENGINE_VERSION GAME_NAME " " __DATE__ " " __TIME__ // printed in console // paths #define CD_BASEDIR "Doom" #ifdef ID_DEMO_BUILD #define BASE_GAMEDIR "demo" +#else +#ifdef PREY + #define BASE_GAMEDIR "preybase" #else #define BASE_GAMEDIR "base" #endif +#endif // filenames #define CD_EXE "doom.exe" + +#ifdef PREY +#define CONFIG_FILE "PreyConfig.cfg" +#else #define CONFIG_FILE "DoomConfig.cfg" +#endif // base folder where the source code lives #define SOURCE_CODE_BASE_FOLDER "neo" @@ -90,7 +103,7 @@ If you have questions concerning this license or the applicable additional terms // editor info #define EDITOR_DEFAULT_PROJECT "doom.qe4" #define EDITOR_REGISTRY_KEY "DOOMRadiant" -#define EDITOR_WINDOWTEXT "IceEdit2" +#define EDITOR_WINDOWTEXT "IceEdit2(for " GAME_NAME " )" // win32 info #define WIN32_CONSOLE_CLASS "DOOM 3 WinConsole" diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp index b709e3b7..630b7b8c 100644 --- a/neo/framework/Session.cpp +++ b/neo/framework/Session.cpp @@ -1444,7 +1444,9 @@ void idSessionLocal::LoadLoadingGui( const char *mapName ) { 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 +#ifndef PREY game->GetMapLoadingGUI( guiMap ); +#endif if ( uiManager->CheckGui( guiMap ) ) { guiLoading = uiManager->FindGui( guiMap, true, false, true ); diff --git a/neo/framework/Session_menu.cpp b/neo/framework/Session_menu.cpp index efeccf45..dc6384e8 100644 --- a/neo/framework/Session_menu.cpp +++ b/neo/framework/Session_menu.cpp @@ -576,9 +576,11 @@ void idSessionLocal::HandleMainMenuCommands( const char *menuCommand ) { } // always let the game know the command is being run +#ifndef PREY if ( game ) { game->HandleMainMenuCommands( cmd, guiActive ); } +#endif if ( !idStr::Icmp( cmd, "startGame" ) ) { cvarSystem->SetCVarInteger( "g_skill", guiMainMenu->State().GetInt( "skill" ) ); diff --git a/neo/framework/UsercmdGen.h b/neo/framework/UsercmdGen.h index d248bd44..a0e8b4b0 100644 --- a/neo/framework/UsercmdGen.h +++ b/neo/framework/UsercmdGen.h @@ -44,9 +44,15 @@ const int USERCMD_MSEC = 1000 / USERCMD_HZ; const int BUTTON_ATTACK = BIT(0); const int BUTTON_RUN = BIT(1); const int BUTTON_ZOOM = BIT(2); +#ifdef PREY +const int BUTTON_ATTACK_ALT = BIT(3); +const int BUTTON_SCORES = BIT(4); +const int BUTTON_MLOOK = BIT(5); +#else const int BUTTON_SCORES = BIT(3); const int BUTTON_MLOOK = BIT(4); const int BUTTON_5 = BIT(5); +#endif const int BUTTON_6 = BIT(6); const int BUTTON_7 = BIT(7); diff --git a/neo/framework/async/AsyncClient.cpp b/neo/framework/async/AsyncClient.cpp index 776d895d..7f721b3d 100644 --- a/neo/framework/async/AsyncClient.cpp +++ b/neo/framework/async/AsyncClient.cpp @@ -1880,7 +1880,11 @@ void idAsyncClient::RunFrame( void ) { bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < USERCMD_MSEC ); // run client prediction +#ifdef PREY + gameReturn_t ret = game->ClientPrediction(clientNum, userCmds[snapshotGameFrame & (MAX_USERCMD_BACKUP - 1)]); +#else gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame ); +#endif idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand ); diff --git a/neo/framework/async/AsyncServer.cpp b/neo/framework/async/AsyncServer.cpp index 31ada75b..95be59fd 100644 --- a/neo/framework/async/AsyncServer.cpp +++ b/neo/framework/async/AsyncServer.cpp @@ -255,7 +255,11 @@ void idAsyncServer::ExecuteMapChange( void ) { fileSystem->ClearPureChecksums(); // make sure the map/gametype combo is good +#ifdef PREY + strcpy(bestGameType, game->GetBestGameType(cvarSystem->GetCVarString("si_map"), cvarSystem->GetCVarString("si_gametype"))); +#else game->GetBestGameType( cvarSystem->GetCVarString("si_map"), cvarSystem->GetCVarString("si_gametype"), bestGameType ); +#endif cvarSystem->SetCVarString("si_gametype", bestGameType ); // initialize map settings @@ -695,7 +699,11 @@ void idAsyncServer::InitClient( int clientNum, int clientId, int clientRate ) { } // let the game know a player connected +#ifdef PREY + game->ServerClientConnect(clientNum); +#else game->ServerClientConnect( clientNum, client.guid ); +#endif } /* diff --git a/neo/game.vcxproj b/neo/game.vcxproj index 569583fa..c6e0dbe8 100644 --- a/neo/game.vcxproj +++ b/neo/game.vcxproj @@ -25,6 +25,10 @@ Dedicated Release x64 + + Prey Debug + x64 + Release x64 @@ -79,6 +83,11 @@ MultiByte v145 + + DynamicLibrary + MultiByte + v145 + @@ -131,6 +140,12 @@ + + + + + + <_ProjectFileVersion>10.0.40219.1 @@ -141,8 +156,11 @@ AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset @@ -171,6 +189,21 @@ precompiled.h + + + + + + + ../gamex64.dll + + + Use + + + precompiled.h + + @@ -333,6 +366,7 @@ Create + Create Create Create Create diff --git a/neo/idlib.vcxproj b/neo/idlib.vcxproj index 9fd0d007..c9b75af3 100644 --- a/neo/idlib.vcxproj +++ b/neo/idlib.vcxproj @@ -25,6 +25,10 @@ Dedicated Release x64 + + Prey Debug + x64 + Release x64 @@ -79,6 +83,11 @@ MultiByte v145 + + StaticLibrary + MultiByte + v145 + @@ -131,6 +140,12 @@ + + + + + + <_ProjectFileVersion>10.0.40219.1 @@ -141,8 +156,11 @@ AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset @@ -156,7 +174,10 @@ - + + + PREY;_DEBUG;%(PreprocessorDefinitions) + @@ -206,8 +227,11 @@ Create Create Create + Create + + Create Create Create diff --git a/neo/idlib/BitMsg.h b/neo/idlib/BitMsg.h index 8fc7be05..67382719 100644 --- a/neo/idlib/BitMsg.h +++ b/neo/idlib/BitMsg.h @@ -119,6 +119,14 @@ public: int ReadData( void *data, int length ) const; void ReadNetadr( netadr_t *adr ) const; + void WriteVec3(const idVec3& vector); + void WriteMat3(const idMat3& axis); + void WriteBool(bool boolean); + + idVec3 ReadVec3() const; + idMat3 ReadMat3() const; + bool ReadBool() const; + int ReadDeltaChar( int oldValue ) const; int ReadDeltaByte( int oldValue ) const; int ReadDeltaShort( int oldValue ) const; @@ -427,6 +435,42 @@ ID_INLINE float idBitMsg::ReadDeltaFloat( float oldValue, int exponentBits, int return idMath::BitsToFloat( newBits, exponentBits, mantissaBits ); } +ID_INLINE void idBitMsg::WriteVec3(const idVec3& vector) { + WriteFloat(vector[0]); + WriteFloat(vector[1]); + WriteFloat(vector[2]); +} + +ID_INLINE void idBitMsg::WriteMat3(const idMat3& axis) { + WriteVec3(axis[0]); + WriteVec3(axis[1]); + WriteVec3(axis[2]); +} + +ID_INLINE void idBitMsg::WriteBool(bool boolean) { + WriteBits(boolean, 1); +} + +ID_INLINE idVec3 idBitMsg::ReadVec3() const { + idVec3 vector; + vector[0] = ReadFloat(); + vector[1] = ReadFloat(); + vector[2] = ReadFloat(); + return vector; +} + +ID_INLINE idMat3 idBitMsg::ReadMat3() const { + idMat3 axis; + axis[0] = ReadVec3(); + axis[1] = ReadVec3(); + axis[2] = ReadVec3(); + return axis; +} + +ID_INLINE bool idBitMsg::ReadBool() const { + int boolean = ReadBits(1); + return *(bool*)&boolean;//We seem to get a performance warning unless I do this. Ick! +} /* =============================================================================== diff --git a/neo/idlib/containers/List.h b/neo/idlib/containers/List.h index 59a550fd..c0c22f88 100644 --- a/neo/idlib/containers/List.h +++ b/neo/idlib/containers/List.h @@ -131,7 +131,7 @@ public: void Swap( idList &other ); // swap the contents of the lists void DeleteContents( bool clear ); // delete the contents of the list -private: +protected: int num; int size; int granularity; diff --git a/neo/idlib/math/Vector.cpp b/neo/idlib/math/Vector.cpp index 469b5628..f1c168d2 100644 --- a/neo/idlib/math/Vector.cpp +++ b/neo/idlib/math/Vector.cpp @@ -213,6 +213,42 @@ idMat3 idVec3::ToMat3( void ) const { return mat; } +#ifdef PREY +// HUMANHEAD nla +/* +============= +idVec3::hhToMat3 +============= +*/ +idMat3 idVec3::hhToMat3(void) const { + idVec3 left; + idVec3 down; + + // NormalVectors actually returns left and down + NormalVectors(left, down); + return idMat3(*this, left, -down); +} + +// HUMANHEAD PDM: Vector conversion to/from axis aligned direction masks (6-bit vector compression) +int idVec3::DirectionMask() const { + int mask = 0; + for (int term = 0; term < 3; term++) { + mask += (*this)[term] < 0 ? 1 << (term << 1) : (*this)[term]>0 ? 1 << ((term << 1) + 1) : 0; + } + return mask; +} + +idVec3::idVec3(int directionMask) { + int negativeMask, positiveMask; + for (int term = 0; term < 3; term++) { + negativeMask = 1 << (term << 1); + positiveMask = 1 << ((term << 1) + 1); + (*this)[term] = (directionMask & negativeMask) ? -1 : (directionMask & positiveMask) ? 1 : 0; + } +} +// HUMANHEAD END +#endif + /* ============= idVec3::ToString diff --git a/neo/idlib/math/Vector.h b/neo/idlib/math/Vector.h index 8ef454dc..4ac114ca 100644 --- a/neo/idlib/math/Vector.h +++ b/neo/idlib/math/Vector.h @@ -313,6 +313,18 @@ ID_INLINE float *idVec2::ToFloatPtr( void ) { // //=============================================================== +#ifdef PREY +// HUMANHEAD pdm: support for 6 bit vector compression, direction masks +#define INDEX_IN_MASK(mask, index) ( ((mask) & (1<<(index)) ) != 0 ) +#define MASK_NEGX 0x0001 +#define MASK_POSX 0x0002 +#define MASK_NEGY 0x0004 +#define MASK_POSY 0x0008 +#define MASK_NEGZ 0x0010 +#define MASK_POSZ 0x0020 +// HUMANHEAD END +#endif + class idVec3 { public: float x; @@ -384,11 +396,29 @@ public: void Lerp( const idVec3 &v1, const idVec3 &v2, const float l ); void SLerp( const idVec3 &v1, const idVec3 &v2, const float l ); + + idVec3 ToNormal() const; +#ifdef PREY + // HUMANHEAD nla - Returns a Mat3 with Z up instead of down + idMat3 hhToMat3(void) const; // vector should be normalized + int DirectionMask() const; // calculate direction bitmask + idVec3(int directionMask); // construct from direction bitmask + // HUMANHEAD END +#endif }; extern idVec3 vec3_origin; #define vec3_zero vec3_origin + +ID_INLINE idVec3 idVec3::ToNormal() const { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; + invLength = idMath::InvSqrt(sqrLength); + return *this * invLength; +} + ID_INLINE idVec3::idVec3( void ) { } diff --git a/neo/idlib/precompiled.h b/neo/idlib/precompiled.h index 9ba6f56a..d0c57a9a 100644 --- a/neo/idlib/precompiled.h +++ b/neo/idlib/precompiled.h @@ -163,30 +163,41 @@ const int MAX_EXPRESSION_REGISTERS = 4096; #include "../ui/UserInterface.h" // collision detection system +#if defined(PREY) +#include "../prey/cm/CollisionModel.h" +#else #include "../cm/CollisionModel.h" +#endif // AAS files and manager #include "../tools/compilers/aas/AASFile.h" #include "../tools/compilers/aas/AASFileManager.h" // game +#if defined(PREY) +#include "../prey/game/Game.h" +#else #if defined(_D3XP) #include "../d3xp/Game.h" #else #include "../game/Game.h" #endif - +#endif //----------------------------------------------------- #ifndef _D3SDK #ifdef GAME_DLL +#if defined(PREY) +#include "../prey/game/Game_local.h" +#else #if defined(_D3XP) #include "../d3xp/Game_local.h" #else #include "../game/Game_local.h" #endif +#endif #else diff --git a/neo/opengl/opengl.vcxproj b/neo/opengl/opengl.vcxproj index 5e8e6e5c..01da4388 100644 --- a/neo/opengl/opengl.vcxproj +++ b/neo/opengl/opengl.vcxproj @@ -5,6 +5,14 @@ Debug Win32 + + Prey Debug + Win32 + + + Prey Debug + x64 + Release Win32 @@ -22,69 +30,106 @@ Disabled + Disabled MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default MaxSpeed + MaxSpeed Speed + Speed Default + Default @@ -127,6 +172,12 @@ v145 Unicode + + StaticLibrary + true + v145 + Unicode + StaticLibrary false @@ -140,6 +191,12 @@ v145 Unicode + + StaticLibrary + true + v145 + Unicode + StaticLibrary false @@ -155,12 +212,18 @@ + + + + + + @@ -181,6 +244,22 @@ true + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + + + + + true + + Level3 @@ -218,6 +297,25 @@ true + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + stdcpp20 + NotUsing + pch.h + MultiThreadedDebug + streamline/include + 4996 + + + + + true + + Level3 diff --git a/neo/prey/MayaImport/Maya4.5/maya.h b/neo/prey/MayaImport/Maya4.5/maya.h new file mode 100644 index 00000000..91ad4ba4 --- /dev/null +++ b/neo/prey/MayaImport/Maya4.5/maya.h @@ -0,0 +1,47 @@ +#ifdef _WIN32 + +#define _BOOL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef _BOOL + +#endif // _WIN32 diff --git a/neo/prey/MayaImport/Maya6.0/maya.h b/neo/prey/MayaImport/Maya6.0/maya.h new file mode 100644 index 00000000..91ad4ba4 --- /dev/null +++ b/neo/prey/MayaImport/Maya6.0/maya.h @@ -0,0 +1,47 @@ +#ifdef _WIN32 + +#define _BOOL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef _BOOL + +#endif // _WIN32 diff --git a/neo/prey/MayaImport/exporter.h b/neo/prey/MayaImport/exporter.h new file mode 100644 index 00000000..d3ec82df --- /dev/null +++ b/neo/prey/MayaImport/exporter.h @@ -0,0 +1,434 @@ +#define MAYA_DEFAULT_CAMERA "camera1" + +#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 { + WRITE_MESH, + WRITE_ANIM, + WRITE_CAMERA +} exportType_t; + +typedef struct { + idCQuat q; + idVec3 t; +} jointFrame_t; + +typedef struct { + idCQuat q; + idVec3 t; + float fov; +} cameraFrame_t; + +/* +============================================================================================== + + idTokenizer + +============================================================================================== +*/ + +class idTokenizer { +private: + int currentToken; + idStrList tokens; + +public: + idTokenizer() { Clear(); }; + void Clear( void ) { currentToken = 0; tokens.Clear(); }; + + int SetTokens( const char *buffer ); + const char *NextToken( const char *errorstring = NULL ); + + bool TokenAvailable( void ) { return currentToken < tokens.Num(); }; + int Num( void ) { return tokens.Num(); }; + void UnGetToken( void ) { if ( currentToken > 0 ) { currentToken--; } }; + const char *GetToken( int index ) { if ( ( index >= 0 ) && ( index < tokens.Num() ) ) { return tokens[ index ]; } else { return NULL; } }; + const char *CurrentToken( void ) { return GetToken( currentToken ); }; +}; + +/* +============================================================================================== + + idExportOptions + +============================================================================================== +*/ + +class idNamePair { +public: + idStr from; + idStr to; +}; + +class idAnimGroup { +public: + idStr name; + idStrList joints; +}; + +class idExportOptions { +private: + idTokenizer tokens; + + void Reset( const char *commandline ); + +public: + idStr commandLine; + idStr src; + idStr dest; + idStr game; + idStr prefix; + float scale; + exportType_t type; + bool ignoreMeshes; + bool clearOrigin; + bool clearOriginAxis; + bool ignoreScale; + int startframe; + int endframe; + int framerate; + float xyzPrecision; + float quatPrecision; + idStr align; + idList renamejoints; + idList remapjoints; + idStrList keepjoints; + idStrList skipmeshes; + idStrList keepmeshes; + idList exportgroups; + idList groups; + float rotate; + float jointThreshold; + int cycleStart; + + // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + float boundsExpansion; + // HUMANHEAD END + + idExportOptions( const char *commandline, const char *ospath ); + + bool jointInExportGroup( const char *jointname ); +}; + +/* +============================================================================== + +idExportJoint + +============================================================================== +*/ + +class idExportJoint { +public: + idStr name; + idStr realname; + idStr longname; + int index; + int exportNum; + bool keep; + + float scale; + float invscale; + + MFnDagNode *dagnode; + + idHierarchy mayaNode; + idHierarchy exportNode; + + idVec3 t; + idMat3 wm; + + idVec3 idt; + idMat3 idwm; + + idVec3 bindpos; + idMat3 bindmat; + + int animBits; + int firstComponent; + jointFrame_t baseFrame; + int depth; + + idExportJoint(); + idExportJoint &operator=( const idExportJoint &other ); +}; + +/* +============================================================================== + +misc structures + +============================================================================== +*/ + +typedef struct { + idExportJoint *joint; + float jointWeight; + idVec3 offset; +} exportWeight_t; + +typedef struct { + idVec3 pos; + idVec2 texCoords; + int startweight; + int numWeights; +} exportVertex_t; + +typedef struct { + int indexes[ 3 ]; +} exportTriangle_t; + +typedef struct { + idVec2 uv[ 3 ]; +} exportUV_t; + +ID_INLINE int operator==( exportVertex_t a, exportVertex_t b ) { + if ( a.pos != b.pos ) { + return false; + } + + if ( ( a.texCoords[ 0 ] != b.texCoords[ 0 ] ) || ( a.texCoords[ 1 ] != b.texCoords[ 1 ] ) ) { + return false; + } + + if ( ( a.startweight != b.startweight ) || ( a.numWeights != b.numWeights ) ) { + return false; + } + + return true; +} + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// 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 + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + + +// the maximum size of game reletive pathnames +#define MAX_Q3PATH 64 + +typedef struct md3Frame_s { + idVec3 bounds[2]; + idVec3 localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_Q3PATH]; // 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 { + int ident; // + + char name[MAX_Q3PATH]; // 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_Q3PATH]; + int shaderIndex; // 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 { + int ident; + int version; + + char name[MAX_Q3PATH]; // 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; + +/* +============================================================================== + +idExportMesh + +============================================================================== +*/ + +class idExportMesh { +public: + + idStr name; + idStr shader; + + bool keep; + + idList verts; + idList tris; + idList weights; + idList uv; + + idExportMesh() { keep = true; }; + void ShareVerts( void ); + void GetBounds( idBounds &bounds ) const; + void Merge( idExportMesh *mesh ); +}; + +/* +============================================================================== + +idExportModel + +============================================================================== +*/ + +class idExportModel { +public: + idExportJoint *exportOrigin; + idList joints; + idHierarchy mayaHead; + idHierarchy exportHead; + idList cameraCuts; + idList camera; + idList bounds; + idList jointFrames; + idList frames; + int frameRate; + int numFrames; + int skipjoints; + int export_joints; + idList meshes; + + idExportModel(); + ~idExportModel(); + idExportJoint *FindJointReal( const char *name ); + idExportJoint *FindJoint( const char *name ); + bool WriteMesh( const char *filename, idExportOptions &options ); + bool WriteAnim( const char *filename, idExportOptions &options ); + bool WriteCamera( const char *filename, idExportOptions &options ); +}; + +/* +============================================================================== + +Maya + +============================================================================== +*/ + +class idMayaExport { +private: + idExportModel model; + idExportOptions &options; + + void FreeDagNodes( void ); + + float TimeForFrame( int num ) const; + int GetMayaFrameNum( int num ) const; + void SetFrame( int num ); + + + void GetBindPose( MObject &jointNode, idExportJoint *joint, float scale ); + void GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat ); + void GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale ); + + void CreateJoints( float scale ); + void PruneJoints( idStrList &keepjoints, idStr &prefix ); + void RenameJoints( idList &renamejoints, idStr &prefix ); + bool RemapParents( idList &remapjoints ); + + MObject FindShader( MObject& setNode ); + void GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode ); + + idExportMesh *CopyMesh( MFnSkinCluster &skinCluster, float scale ); + void CreateMesh( float scale ); + void CombineMeshes( void ); + + void GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe ); + + const char *GetObjectType( MObject object ); + + float GetCameraFov( idExportJoint *joint ); + void GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam ); + void CreateCameraAnim( idMat3 &align ); + + void GetDefaultPose( idMat3 &align ); + void CreateAnimation( idMat3 &align ); + +public: + idMayaExport( idExportOptions &exportOptions ) : options( exportOptions ) { }; + ~idMayaExport(); + + void ConvertModel( void ); + void ConvertToMD3( void ); +}; diff --git a/neo/prey/MayaImport/maya5.0/maya.h b/neo/prey/MayaImport/maya5.0/maya.h new file mode 100644 index 00000000..cd007cb7 --- /dev/null +++ b/neo/prey/MayaImport/maya5.0/maya.h @@ -0,0 +1,47 @@ +#ifdef _WIN32 + +#define _BOOL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef _BOOL + +#endif // _WIN32 diff --git a/neo/prey/MayaImport/maya_main.cpp b/neo/prey/MayaImport/maya_main.cpp new file mode 100644 index 00000000..3794fc37 --- /dev/null +++ b/neo/prey/MayaImport/maya_main.cpp @@ -0,0 +1,3152 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +// HUMANHEAD CJR : Set-up for Maya 6.0 +#define USE_MAYA_6 1 + +#if USE_MAYA_6 +#undef _GLOBAL_USING +#define _GLOBAL_USING 0 +#include "Maya6.0/maya.h" // must also change include directory in project from "MayaImport\Maya4.5\include" to "MayaImport\Maya6.0\include" (requires MSDev 7.1) +#else +#include "Maya5.0/maya.h" +#endif +// HUMANHEAD END + +#include "exporter.h" +#include "maya_main.h" + +idStr errorMessage; +bool initialized = false; + +#define DEFAULT_ANIM_EPSILON 0.125f +#define DEFAULT_QUAT_EPSILON ( 1.0f / 8192.0f ) + +#define SLOP_VERTEX 0.01f // merge xyz coordinates this far apart +#define SLOP_TEXCOORD 0.001f // merge texture coordinates this far apart + +const char *componentNames[ 6 ] = { "Tx", "Ty", "Tz", "Qx", "Qy", "Qz" }; + +idSys * sys = NULL; +idCommon * common = NULL; +idCVarSystem * cvarSystem = NULL; + +idCVar * idCVar::staticVars = NULL; + +// HUMANHEAD pdm: Output all the animation text with Green color +#define Printf APrintf +// HUMANHEAD END + +/* +================= +MayaError +================= +*/ +void MayaError( const char *fmt, ... ) { + va_list argptr; + char text[ 8192 ]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + throw idException( text ); +} + +/* +================= +FS_WriteFloatString +================= +*/ +#define MAX_PRINT_MSG 4096 +static int WriteFloatString( FILE *file, const char *fmt, ... ) { + long i; + unsigned long u; + double f; + char *str; + int index; + idStr tmp, format; + va_list argPtr; + + va_start( argPtr, fmt ); + + 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 += fprintf( file, "%s", tmp.c_str() ); + } + else { + index += fprintf( file, format.c_str(), f ); + } + break; + case 'd': + case 'i': + i = va_arg( argPtr, long ); + index += fprintf( file, format.c_str(), i ); + break; + case 'u': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'o': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'x': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'X': + u = va_arg( argPtr, unsigned long ); + index += fprintf( file, format.c_str(), u ); + break; + case 'c': + i = va_arg( argPtr, long ); + index += fprintf( file, format.c_str(), (char) i ); + break; + case 's': + str = va_arg( argPtr, char * ); + index += fprintf( file, format.c_str(), str ); + break; + case '%': + index += fprintf( file, format.c_str() ); + break; + default: + MayaError( "WriteFloatString: invalid format %s", format.c_str() ); + break; + } + fmt++; + break; + case '\\': + fmt++; + switch( *fmt ) { + case 't': + index += fprintf( file, "\t" ); + break; + case 'n': + index += fprintf( file, "\n" ); + default: + MayaError( "WriteFloatString: unknown escape character \'%c\'", *fmt ); + break; + } + fmt++; + break; + default: + index += fprintf( file, "%c", *fmt ); + fmt++; + break; + } + } + + va_end( argPtr ); + + return index; +} + +/* +================ +OSPathToRelativePath + +takes a full OS path, as might be found in data from a media creation +program, and converts it to a qpath by stripping off directories + +Returns false if the osPath tree doesn't match any of the existing +search paths. +================ +*/ +bool OSPathToRelativePath( const char *osPath, idStr &qpath, const char *game ) { + char *s, *base; + + // skip a drive letter? + + // search for anything with BASE_GAMEDIR 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 + base = (char *)strstr( osPath, BASE_GAMEDIR ); //HUMANHEAD rww vs2005 - strstr now returns const char + + // _D3XP added mod support + if ( base == NULL && strlen(game) > 0 ) { + + base = s = (char *)strstr( osPath, game ); //HUMANHEAD rww vs2005 - strstr now returns const char + + while( s = strstr( s, game ) ) { + s += strlen( game ); + if ( s[0] == '/' || s[0] == '\\' ) { + base = s; + } + } + } + + if ( base ) { + s = strstr( base, "/" ); + if ( !s ) { + s = strstr( base, "\\" ); + } + if ( s ) { + qpath = s + 1; + return true; + } + } + + common->Printf( "OSPathToRelativePath failed on %s\n", osPath ); + qpath = osPath; + + return false; +} + +/* +=============== +ConvertFromIdSpace +=============== +*/ +idMat3 ConvertFromIdSpace( const idMat3 &idmat ) { + idMat3 mat; + + mat[ 0 ][ 0 ] = idmat[ 0 ][ 0 ]; + mat[ 0 ][ 2 ] = -idmat[ 0 ][ 1 ]; + mat[ 0 ][ 1 ] = idmat[ 0 ][ 2 ]; + + mat[ 1 ][ 0 ] = idmat[ 1 ][ 0 ]; + mat[ 1 ][ 2 ] = -idmat[ 1 ][ 1 ]; + mat[ 1 ][ 1 ] = idmat[ 1 ][ 2 ]; + + mat[ 2 ][ 0 ] = idmat[ 2 ][ 0 ]; + mat[ 2 ][ 2 ] = -idmat[ 2 ][ 1 ]; + mat[ 2 ][ 1 ] = idmat[ 2 ][ 2 ]; + + return mat; +} + +/* +=============== +ConvertFromIdSpace +=============== +*/ +idVec3 ConvertFromIdSpace( const idVec3 &idpos ) { + idVec3 pos; + + pos.x = idpos.x; + pos.z = -idpos.y; + pos.y = idpos.z; + + return pos; +} + +/* +=============== +ConvertToIdSpace +=============== +*/ +idMat3 ConvertToIdSpace( const idMat3 &mat ) { + idMat3 idmat; + + idmat[ 0 ][ 0 ] = mat[ 0 ][ 0 ]; + idmat[ 0 ][ 1 ] = -mat[ 0 ][ 2 ]; + idmat[ 0 ][ 2 ] = mat[ 0 ][ 1 ]; + + idmat[ 1 ][ 0 ] = mat[ 1 ][ 0 ]; + idmat[ 1 ][ 1 ] = -mat[ 1 ][ 2 ]; + idmat[ 1 ][ 2 ] = mat[ 1 ][ 1 ]; + + idmat[ 2 ][ 0 ] = mat[ 2 ][ 0 ]; + idmat[ 2 ][ 1 ] = -mat[ 2 ][ 2 ]; + idmat[ 2 ][ 2 ] = mat[ 2 ][ 1 ]; + + return idmat; +} + +/* +=============== +ConvertToIdSpace +=============== +*/ +idVec3 ConvertToIdSpace( const idVec3 &pos ) { + idVec3 idpos; + + idpos.x = pos.x; + idpos.y = -pos.z; + idpos.z = pos.y; + + return idpos; +} + +/* +=============== +idVec +=============== +*/ +idVec3 idVec( const MFloatPoint &point ) { + return idVec3( point[ 0 ], point[ 1 ], point[ 2 ] ); +} + +/* +=============== +idVec +=============== +*/ +idVec3 idVec( const MMatrix &matrix ) { + return idVec3( matrix[ 3 ][ 0 ], matrix[ 3 ][ 1 ], matrix[ 3 ][ 2 ] ); +} + +/* +=============== +idMat +=============== +*/ +idMat3 idMat( const MMatrix &matrix ) { + int j, k; + idMat3 mat; + + for( j = 0; j < 3; j++ ) { + for( k = 0; k < 3; k++ ) { + mat[ j ][ k ] = matrix[ j ][ k ]; + } + } + + return mat; +} + +/* +=============== +GetParent +=============== +*/ +MFnDagNode *GetParent( MFnDagNode *joint ) { + MStatus status; + MObject parentObject; + + parentObject = joint->parent( 0, &status ); + if ( !status && status.statusCode() == MStatus::kInvalidParameter ) { + return NULL; + } + + while( !parentObject.hasFn( MFn::kTransform ) ) { + MFnDagNode parentNode( parentObject, &status ); + if ( !status ) { + return NULL; + } + + parentObject = parentNode.parent( 0, &status ); + if ( !status && status.statusCode() == MStatus::kInvalidParameter ) { + return NULL; + } + } + + MFnDagNode *parentNode; + + parentNode = new MFnDagNode( parentObject, &status ); + if ( !status ) { + delete parentNode; + return NULL; + } + + return parentNode; +} + +/* +============================================================================================== + + idTokenizer + +============================================================================================== +*/ + +/* +==================== +idTokenizer::SetTokens +==================== +*/ +int idTokenizer::SetTokens( const char *buffer ) { + const char *cmd; + + Clear(); + + // tokenize commandline + cmd = buffer; + while ( *cmd ) { + // skip whitespace + while( *cmd && isspace( *cmd ) ) { + cmd++; + } + + if ( !*cmd ) { + break; + } + + idStr ¤t = tokens.Alloc(); + while( *cmd && !isspace( *cmd ) ) { + current += *cmd; + cmd++; + } + } + + return tokens.Num(); +} + +/* +==================== +idTokenizer::NextToken +==================== +*/ +const char *idTokenizer::NextToken( const char *errorstring ) { + if ( currentToken < tokens.Num() ) { + return tokens[ currentToken++ ]; + } + + if ( errorstring ) { + MayaError( "Error: %s", errorstring ); + } + + return NULL; +} + +/* +============================================================================================== + + idExportOptions + +============================================================================================== +*/ + +/* +==================== +idExportOptions::Reset +==================== +*/ +void idExportOptions::Reset( const char *commandline ) { + scale = 1.0f; + type = WRITE_MESH; + startframe = -1; + endframe = -1; + ignoreMeshes = false; + clearOrigin = false; + clearOriginAxis = false; + framerate = 24; + align = ""; + rotate = 0.0f; + commandLine = commandline; + prefix = ""; + jointThreshold = 0.05f; + ignoreScale = false; + xyzPrecision = DEFAULT_ANIM_EPSILON; + quatPrecision = DEFAULT_QUAT_EPSILON; + cycleStart = -1; + + // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + boundsExpansion = 0.0f; + // HUMANHEAD END + + src.Clear(); + dest.Clear(); + + tokens.SetTokens( commandline ); + + keepjoints.Clear(); + renamejoints.Clear(); + remapjoints.Clear(); + exportgroups.Clear(); + skipmeshes.Clear(); + keepmeshes.Clear(); + groups.Clear(); +} + +/* +==================== +idExportOptions::idExportOptions +==================== +*/ +idExportOptions::idExportOptions( const char *commandline, const char *ospath ) { + idStr token; + idNamePair joints; + int i; + idAnimGroup *group; + idStr sourceDir; + idStr destDir; + + Reset( commandline ); + + token = tokens.NextToken( "Missing export command" ); + if ( token == "mesh" ) { + type = WRITE_MESH; + } else if ( token == "anim" ) { + type = WRITE_ANIM; + } else if ( token == "camera" ) { + type = WRITE_CAMERA; + } else { + MayaError( "Unknown export command '%s'", token.c_str() ); + } + + src = tokens.NextToken( "Missing source filename" ); + dest = src; + + for( token = tokens.NextToken(); token != ""; token = tokens.NextToken() ) { + if ( token == "-force" ) { + // skip + } else if ( token == "-game" ) { + // parse game name + game = tokens.NextToken( "Expecting game name after -game" ); + + } else if ( token == "-rename" ) { + // parse joint to rename + joints.from = tokens.NextToken( "Missing joint name for -rename. Usage: -rename [joint name] [new name]" ); + joints.to = tokens.NextToken( "Missing new name for -rename. Usage: -rename [joint name] [new name]" ); + renamejoints.Append( joints ); + + } else if ( token == "-prefix" ) { + prefix = tokens.NextToken( "Missing name for -prefix. Usage: -prefix [joint prefix]" ); + + } else if ( token == "-parent" ) { + // parse joint to reparent + joints.from = tokens.NextToken( "Missing joint name for -parent. Usage: -parent [joint name] [new parent]" ); + joints.to = tokens.NextToken( "Missing new parent for -parent. Usage: -parent [joint name] [new parent]" ); + remapjoints.Append( joints ); + + } else if ( !token.Icmp( "-sourcedir" ) ) { + // parse source directory + sourceDir = tokens.NextToken( "Missing filename for -sourcedir. Usage: -sourcedir [directory]" ); + + } else if ( !token.Icmp( "-destdir" ) ) { + // parse destination directory + destDir = tokens.NextToken( "Missing filename for -destdir. Usage: -destdir [directory]" ); + + } else if ( token == "-dest" ) { + // parse destination filename + dest = tokens.NextToken( "Missing filename for -dest. Usage: -dest [filename]" ); + + } else if ( token == "-range" ) { + // parse frame range to export + token = tokens.NextToken( "Missing start frame for -range. Usage: -range [start frame] [end frame]" ); + startframe = atoi( token ); + token = tokens.NextToken( "Missing end frame for -range. Usage: -range [start frame] [end frame]" ); + endframe = atoi( token ); + + if ( startframe > endframe ) { + MayaError( "Start frame is greater than end frame." ); + } + + } else if ( !token.Icmp( "-cycleStart" ) ) { + // parse start frame of cycle + token = tokens.NextToken( "Missing cycle start frame for -cycleStart. Usage: -cycleStart [first frame of cycle]" ); + cycleStart = atoi( token ); + + } else if ( token == "-scale" ) { + // parse scale + token = tokens.NextToken( "Missing scale amount for -scale. Usage: -scale [scale amount]" ); + scale = atof( token ); + + } else if ( token == "-align" ) { + // parse align joint + align = tokens.NextToken( "Missing joint name for -align. Usage: -align [joint name]" ); + + // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + } else if ( token == "-expandbounds" ) { + // parse boundsexpansion + token = tokens.NextToken( "Missing expansion amount for -expandbounds. Usage: -expandbounds [expansion amount]" ); + boundsExpansion = atof( token ); + // HUMANHEAD END + + } else if ( token == "-rotate" ) { + // parse angle rotation + token = tokens.NextToken( "Missing value for -rotate. Usage: -rotate [yaw]" ); + rotate = -atof( token ); + + } else if ( token == "-nomesh" ) { + ignoreMeshes = true; + + } else if ( token == "-clearorigin" ) { + clearOrigin = true; + clearOriginAxis = true; + + } else if ( token == "-clearoriginaxis" ) { + clearOriginAxis = true; + + } else if ( token == "-ignorescale" ) { + ignoreScale = true; + + } else if ( token == "-xyzprecision" ) { + // parse quaternion precision + token = tokens.NextToken( "Missing value for -xyzprecision. Usage: -xyzprecision [precision]" ); + xyzPrecision = atof( token ); + if ( xyzPrecision < 0.0f ) { + MayaError( "Invalid value for -xyzprecision. Must be >= 0" ); + } + + } else if ( token == "-quatprecision" ) { + // parse quaternion precision + token = tokens.NextToken( "Missing value for -quatprecision. Usage: -quatprecision [precision]" ); + quatPrecision = atof( token ); + if ( quatPrecision < 0.0f ) { + MayaError( "Invalid value for -quatprecision. Must be >= 0" ); + } + + } else if ( token == "-jointthreshold" ) { + // parse joint threshold + token = tokens.NextToken( "Missing weight for -jointthreshold. Usage: -jointthreshold [minimum joint weight]" ); + jointThreshold = atof( token ); + + } else if ( token == "-skipmesh" ) { + token = tokens.NextToken( "Missing name for -skipmesh. Usage: -skipmesh [name of mesh to skip]" ); + skipmeshes.AddUnique( token ); + + } else if ( token == "-keepmesh" ) { + token = tokens.NextToken( "Missing name for -keepmesh. Usage: -keepmesh [name of mesh to keep]" ); + keepmeshes.AddUnique( token ); + + } else if ( token == "-jointgroup" ) { + token = tokens.NextToken( "Missing name for -jointgroup. Usage: -jointgroup [group name] [joint1] [joint2]...[joint n]" ); + group = groups.Ptr(); + for( i = 0; i < groups.Num(); i++, group++ ) { + if ( group->name == token ) { + break; + } + } + + if ( i >= groups.Num() ) { + // create a new group + group = &groups.Alloc(); + group->name = token; + } + + while( tokens.TokenAvailable() ) { + token = tokens.NextToken(); + if ( token[ 0 ] == '-' ) { + tokens.UnGetToken(); + break; + } + + group->joints.AddUnique( token ); + } + } else if ( token == "-group" ) { + // add the list of groups to export (these don't affect the hierarchy) + while( tokens.TokenAvailable() ) { + token = tokens.NextToken(); + if ( token[ 0 ] == '-' ) { + tokens.UnGetToken(); + break; + } + + group = groups.Ptr(); + for( i = 0; i < groups.Num(); i++, group++ ) { + if ( group->name == token ) { + break; + } + } + + if ( i >= groups.Num() ) { + MayaError( "Unknown group '%s'", token.c_str() ); + } + + exportgroups.AddUnique( group ); + } + } else if ( token == "-keep" ) { + // add joints that are kept whether they're used by a mesh or not + while( tokens.TokenAvailable() ) { + token = tokens.NextToken(); + if ( token[ 0 ] == '-' ) { + tokens.UnGetToken(); + break; + } + keepjoints.AddUnique( token ); + } + } else { + MayaError( "Unknown option '%s'", token.c_str() ); + } + } + + token = src; + src = ospath; + src.BackSlashesToSlashes(); + src.AppendPath( sourceDir ); + src.AppendPath( token ); + + token = dest; + dest = ospath; + dest.BackSlashesToSlashes(); + dest.AppendPath( destDir ); + dest.AppendPath( token ); + + // Maya only accepts unix style path separators + src.BackSlashesToSlashes(); + dest.BackSlashesToSlashes(); + + if ( skipmeshes.Num() && keepmeshes.Num() ) { + MayaError( "Can't use -keepmesh and -skipmesh together." ); + } +} + +/* +==================== +idExportOptions::jointInExportGroup +==================== +*/ +bool idExportOptions::jointInExportGroup( const char *jointname ) { + int i; + int j; + idAnimGroup *group; + + if ( !exportgroups.Num() ) { + // if we don't have any groups specified as export then export every joint + return true; + } + + // search through all exported groups to see if this joint is exported + for( i = 0; i < exportgroups.Num(); i++ ) { + group = exportgroups[ i ]; + for( j = 0; j < group->joints.Num(); j++ ) { + if ( group->joints[ j ] == jointname ) { + return true; + } + } + } + + return false; +} + +/* +============================================================================== + +idExportJoint + +============================================================================== +*/ + +idExportJoint::idExportJoint() { + index = 0; + exportNum = 0; + + mayaNode.SetOwner( this ); + exportNode.SetOwner( this ); + + dagnode = NULL; + + t = vec3_zero; + wm = mat3_default; + bindpos = vec3_zero; + bindmat = mat3_default; + keep = false; + scale = 1.0f; + invscale = 1.0f; + animBits = 0; + firstComponent = 0; + baseFrame.q.Set( 0.0f, 0.0f, 0.0f ); + baseFrame.t.Zero(); +} + +idExportJoint &idExportJoint::operator=( const idExportJoint &other ) { + name = other.name; + realname = other.realname; + longname = other.longname; + index = other.index; + exportNum = other.exportNum; + keep = other.keep; + + scale = other.scale; + invscale = other.invscale; + + dagnode = other.dagnode; + + mayaNode = other.mayaNode; + exportNode = other.exportNode; + + t = other.t; + idt = other.idt; + wm = other.wm; + idwm = other.idwm; + bindpos = other.bindpos; + bindmat = other.bindmat; + + animBits = other.animBits; + firstComponent = other.firstComponent; + baseFrame = other.baseFrame; + + mayaNode.SetOwner( this ); + exportNode.SetOwner( this ); + + return *this; +} + +/* +============================================================================== + +idExportMesh + +============================================================================== +*/ + +void idExportMesh::ShareVerts( void ) { + int i, j, k; + exportVertex_t vert; + idList v; + + v = verts; + verts.Clear(); + for( i = 0; i < tris.Num(); i++ ) { + for( j = 0; j < 3; j++ ) { + vert = v[ tris[ i ].indexes[ j ] ]; + vert.texCoords[ 0 ] = uv[ i ].uv[ j ][ 0 ]; + vert.texCoords[ 1 ] = 1.0f - uv[ i ].uv[ j ][ 1 ]; + + for( k = 0; k < verts.Num(); k++ ) { + if ( vert.numWeights != verts[ k ].numWeights ) { + continue; + } + if ( vert.startweight != verts[ k ].startweight ) { + continue; + } + if ( !vert.pos.Compare( verts[ k ].pos, SLOP_VERTEX ) ) { + continue; + } + if ( !vert.texCoords.Compare( verts[ k ].texCoords, SLOP_TEXCOORD ) ) { + continue; + } + + break; + } + + if ( k < verts.Num() ) { + tris[ i ].indexes[ j ] = k; + } else { + tris[ i ].indexes[ j ] = verts.Append( vert ); + } + } + } +} + +void idExportMesh::Merge( idExportMesh *mesh ) { + int i; + int numverts; + int numtris; + int numweights; + int numuvs; + + // merge name + sprintf( name, "%s, %s", name.c_str(), mesh->name.c_str() ); + + // merge verts + numverts = verts.Num(); + verts.SetNum( numverts + mesh->verts.Num() ); + for( i = 0; i < mesh->verts.Num(); i++ ) { + verts[ numverts + i ] = mesh->verts[ i ]; + verts[ numverts + i ].startweight += weights.Num(); + } + + // merge triangles + numtris = tris.Num(); + tris.SetNum( numtris + mesh->tris.Num() ); + for( i = 0; i < mesh->tris.Num(); i++ ) { + tris[ numtris + i ].indexes[ 0 ] = mesh->tris[ i ].indexes[ 0 ] + numverts; + tris[ numtris + i ].indexes[ 1 ] = mesh->tris[ i ].indexes[ 1 ] + numverts; + tris[ numtris + i ].indexes[ 2 ] = mesh->tris[ i ].indexes[ 2 ] + numverts; + } + + // merge weights + numweights = weights.Num(); + weights.SetNum( numweights + mesh->weights.Num() ); + for( i = 0; i < mesh->weights.Num(); i++ ) { + weights[ numweights + i ] = mesh->weights[ i ]; + } + + // merge uvs + numuvs = uv.Num(); + uv .SetNum( numuvs + mesh->uv.Num() ); + for( i = 0; i < mesh->uv.Num(); i++ ) { + uv[ numuvs + i ] = mesh->uv[ i ]; + } +} + +void idExportMesh::GetBounds( idBounds &bounds ) const { + int i; + int j; + idVec3 pos; + const exportWeight_t *weight; + const exportVertex_t *vert; + + bounds.Clear(); + + weight = weights.Ptr(); + vert = verts.Ptr(); + for( i = 0; i < verts.Num(); i++, vert++ ) { + pos.Zero(); + weight = &weights[ vert->startweight ]; + for( j = 0; j < vert->numWeights; j++, weight++ ) { + pos += weight->jointWeight * ( weight->joint->idwm * weight->offset + weight->joint->idt ); + } + bounds.AddPoint( pos ); + } +} + +/* +============================================================================== + +idExportModel + +============================================================================== +*/ + +/* +==================== +idExportModel::idExportModel +==================== +*/ +ID_INLINE idExportModel::idExportModel() { + export_joints = 0; + skipjoints = 0; + frameRate = 24; + numFrames = 0; + exportOrigin = NULL; +} + +/* +==================== +idExportModel::~idExportModel +==================== +*/ +ID_INLINE idExportModel::~idExportModel() { + meshes.DeleteContents( true ); +} + +idExportJoint *idExportModel::FindJointReal( const char *name ) { + idExportJoint *joint; + int i; + + joint = joints.Ptr(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( joint->realname == name ) { + return joint; + } + } + + return NULL; +} + +idExportJoint *idExportModel::FindJoint( const char *name ) { + idExportJoint *joint; + int i; + + joint = joints.Ptr(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( joint->name == name ) { + return joint; + } + } + + return NULL; +} + +bool idExportModel::WriteMesh( const char *filename, idExportOptions &options ) { + int i, j; + int numMeshes; + idExportMesh *mesh; + idExportJoint *joint; + idExportJoint *parent; + idExportJoint *sibling; + FILE *file; + const char *parentName; + int parentNum; + idList jointList; + + file = fopen( filename, "w" ); + if ( !file ) { + return false; + } + + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointList.Append( joint ); + } + + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + sibling = joint->exportNode.GetSibling(); + while( sibling ) { + if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) { + joint->exportNode.MakeSiblingAfter( sibling->exportNode ); + sibling = joint->exportNode.GetSibling(); + } else { + sibling = sibling->exportNode.GetSibling(); + } + } + } + + jointList.Clear(); + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + joint->exportNum = jointList.Append( joint ); + } + + numMeshes = 0; + if ( !options.ignoreMeshes ) { + for( i = 0; i < meshes.Num(); i++ ) { + if ( meshes[ i ]->keep ) { + numMeshes++; + } + } + } + + // write version info + WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION ); + WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() ); + + // write joints + WriteFloatString( file, "numJoints %d\n", jointList.Num() ); + WriteFloatString( file, "numMeshes %d\n\n", numMeshes ); + + WriteFloatString( file, "joints {\n" ); + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + parent = joint->exportNode.GetParent(); + if ( parent ) { + parentNum = parent->exportNum; + parentName = parent->name.c_str(); + } else { + parentNum = -1; + parentName = ""; + } + + idCQuat bindQuat = joint->bindmat.ToQuat().ToCQuat(); + WriteFloatString( file, "\t\"%s\"\t%d ( %f %f %f ) ( %f %f %f )\t\t// %s\n", joint->name.c_str(), parentNum, + joint->bindpos.x, joint->bindpos.y, joint->bindpos.z, bindQuat[ 0 ], bindQuat[ 1 ], bindQuat[ 2 ], parentName ); + } + WriteFloatString( file, "}\n" ); + + // write meshes + for( i = 0; i < meshes.Num(); i++ ) { + mesh = meshes[ i ]; + if ( !mesh->keep ) { + continue; + } + + WriteFloatString( file, "\nmesh {\n" ); + WriteFloatString( file, "\t// meshes: %s\n", mesh->name.c_str() ); + WriteFloatString( file, "\tshader \"%s\"\n", mesh->shader.c_str() ); + + WriteFloatString( file, "\n\tnumverts %d\n", mesh->verts.Num() ); + for( j = 0; j < mesh->verts.Num(); j++ ) { + WriteFloatString( file, "\tvert %d ( %f %f ) %d %d\n", j, mesh->verts[ j ].texCoords[ 0 ], mesh->verts[ j ].texCoords[ 1 ], + mesh->verts[ j ].startweight, mesh->verts[ j ].numWeights ); + } + + WriteFloatString( file, "\n\tnumtris %d\n", mesh->tris.Num() ); + for( j = 0; j < mesh->tris.Num(); j++ ) { + WriteFloatString( file, "\ttri %d %d %d %d\n", j, mesh->tris[ j ].indexes[ 2 ], mesh->tris[ j ].indexes[ 1 ], mesh->tris[ j ].indexes[ 0 ] ); + } + + WriteFloatString( file, "\n\tnumweights %d\n", mesh->weights.Num() ); + for( j = 0; j < mesh->weights.Num(); j++ ) { + exportWeight_t *weight; + + weight = &mesh->weights[ j ]; + WriteFloatString( file, "\tweight %d %d %f ( %f %f %f )\n", j, + weight->joint->exportNum, weight->jointWeight, weight->offset.x, weight->offset.y, weight->offset.z ); + } + + WriteFloatString( file, "}\n" ); + } + + fclose( file ); + + return true; +} + +bool idExportModel::WriteAnim( const char *filename, idExportOptions &options ) { + int i, j; + idExportJoint *joint; + idExportJoint *parent; + idExportJoint *sibling; + jointFrame_t *frame; + FILE *file; + int numAnimatedComponents; + idList jointList; + + file = fopen( filename, "w" ); + if ( !file ) { + return false; + } + + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointList.Append( joint ); + } + + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + sibling = joint->exportNode.GetSibling(); + while( sibling ) { + if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) { + joint->exportNode.MakeSiblingAfter( sibling->exportNode ); + sibling = joint->exportNode.GetSibling(); + } else { + sibling = sibling->exportNode.GetSibling(); + } + } + } + + jointList.Clear(); + for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + joint->exportNum = jointList.Append( joint ); + } + + numAnimatedComponents = 0; + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + joint->exportNum = i; + joint->baseFrame = frames[ 0 ][ joint->index ]; + joint->animBits = 0; + for( j = 1; j < numFrames; j++ ) { + frame = &frames[ j ][ joint->index ]; + if ( fabs( frame->t[ 0 ] - joint->baseFrame.t[ 0 ] ) > options.xyzPrecision ) { + joint->animBits |= ANIM_TX; + } + if ( fabs( frame->t[ 1 ] - joint->baseFrame.t[ 1 ] ) > options.xyzPrecision ) { + joint->animBits |= ANIM_TY; + } + if ( fabs( frame->t[ 2 ] - joint->baseFrame.t[ 2 ] ) > options.xyzPrecision ) { + joint->animBits |= ANIM_TZ; + } + if ( fabs( frame->q[ 0 ] - joint->baseFrame.q[ 0 ] ) > options.quatPrecision ) { + joint->animBits |= ANIM_QX; + } + if ( fabs( frame->q[ 1 ] - joint->baseFrame.q[ 1 ] ) > options.quatPrecision ) { + joint->animBits |= ANIM_QY; + } + if ( fabs( frame->q[ 2 ] - joint->baseFrame.q[ 2 ] ) > options.quatPrecision ) { + joint->animBits |= ANIM_QZ; + } + if ( ( joint->animBits & 63 ) == 63 ) { + break; + } + } + if ( joint->animBits ) { + joint->firstComponent = numAnimatedComponents; + for( j = 0; j < 6; j++ ) { + if ( joint->animBits & BIT( j ) ) { + numAnimatedComponents++; + } + } + } + } + + // write version info + WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION ); + WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() ); + + WriteFloatString( file, "numFrames %d\n", numFrames ); + WriteFloatString( file, "numJoints %d\n", jointList.Num() ); + WriteFloatString( file, "frameRate %d\n", frameRate ); + WriteFloatString( file, "numAnimatedComponents %d\n", numAnimatedComponents ); + + // write out the hierarchy + WriteFloatString( file, "\nhierarchy {\n" ); + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + parent = joint->exportNode.GetParent(); + if ( parent ) { + WriteFloatString( file, "\t\"%s\"\t%d %d %d\t// %s", joint->name.c_str(), parent->exportNum, joint->animBits, joint->firstComponent, parent->name.c_str() ); + } else { + WriteFloatString( file, "\t\"%s\"\t-1 %d %d\t//", joint->name.c_str(), joint->animBits, joint->firstComponent ); + } + + if ( !joint->animBits ) { + WriteFloatString( file, "\n" ); + } else { + WriteFloatString( file, " ( " ); + for( j = 0; j < 6; j++ ) { + if ( joint->animBits & BIT( j ) ) { + WriteFloatString( file, "%s ", componentNames[ j ] ); + } + } + WriteFloatString( file, ")\n" ); + } + } + WriteFloatString( file, "}\n" ); + + // write the frame bounds + WriteFloatString( file, "\nbounds {\n" ); + for( i = 0; i < numFrames; i++ ) { +#if 1 // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop) + idBounds expandedBounds = bounds[i].Expand( options.boundsExpansion ); + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", expandedBounds[ 0 ].x, expandedBounds[ 0 ].y, expandedBounds[ 0 ].z, expandedBounds[ 1 ].x, expandedBounds[ 1 ].y, expandedBounds[ 1 ].z ); +#else // HUMANHEAD END + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", bounds[ i ][ 0 ].x, bounds[ i ][ 0 ].y, bounds[ i ][ 0 ].z, bounds[ i ][ 1 ].x, bounds[ i ][ 1 ].y, bounds[ i ][ 1 ].z ); +#endif + } + WriteFloatString( file, "}\n" ); + + // write the base frame + WriteFloatString( file, "\nbaseframe {\n" ); + for( i = 0; i < jointList.Num(); i++ ) { + joint = jointList[ i ]; + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", joint->baseFrame.t[ 0 ], joint->baseFrame.t[ 1 ], joint->baseFrame.t[ 2 ], + joint->baseFrame.q[ 0 ], joint->baseFrame.q[ 1 ], joint->baseFrame.q[ 2 ] ); + } + WriteFloatString( file, "}\n" ); + + // write the frames + for( i = 0; i < numFrames; i++ ) { + WriteFloatString( file, "\nframe %d {\n", i ); + for( j = 0; j < jointList.Num(); j++ ) { + joint = jointList[ j ]; + frame = &frames[ i ][ joint->index ]; + if ( joint->animBits ) { + WriteFloatString( file, "\t" ); + if ( joint->animBits & ANIM_TX ) { + WriteFloatString( file, " %f", frame->t[ 0 ] ); + } + if ( joint->animBits & ANIM_TY ) { + WriteFloatString( file, " %f", frame->t[ 1 ] ); + } + if ( joint->animBits & ANIM_TZ ) { + WriteFloatString( file, " %f", frame->t[ 2 ] ); + } + if ( joint->animBits & ANIM_QX ) { + WriteFloatString( file, " %f", frame->q[ 0 ] ); + } + if ( joint->animBits & ANIM_QY ) { + WriteFloatString( file, " %f", frame->q[ 1 ] ); + } + if ( joint->animBits & ANIM_QZ ) { + WriteFloatString( file, " %f", frame->q[ 2 ] ); + } + WriteFloatString( file, "\n" ); + } + } + WriteFloatString( file, "}\n" ); + } + + fclose( file ); + + return true; +} + +bool idExportModel::WriteCamera( const char *filename, idExportOptions &options ) { + int i; + FILE *file; + + file = fopen( filename, "w" ); + if ( !file ) { + return false; + } + + // write version info + WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION ); + WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() ); + + WriteFloatString( file, "numFrames %d\n", camera.Num() ); + WriteFloatString( file, "frameRate %d\n", frameRate ); + WriteFloatString( file, "numCuts %d\n", cameraCuts.Num() ); + + // write out the cuts + WriteFloatString( file, "\ncuts {\n" ); + for( i = 0; i < cameraCuts.Num(); i++ ) { + WriteFloatString( file, "\t%d\n", cameraCuts[ i ] ); + } + WriteFloatString( file, "}\n" ); + + // write out the frames + WriteFloatString( file, "\ncamera {\n" ); + cameraFrame_t *frame = camera.Ptr(); + for( i = 0; i < camera.Num(); i++, frame++ ) { + WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f ) %f\n", frame->t.x, frame->t.y, frame->t.z, frame->q[ 0 ], frame->q[ 1 ], frame->q[ 2 ], frame->fov ); + } + WriteFloatString( file, "}\n" ); + + fclose( file ); + + return true; +} + +/* +============================================================================== + +Maya + +============================================================================== +*/ + +/* +=============== +idMayaExport::~idMayaExport + +=============== +*/ +idMayaExport::~idMayaExport() { + FreeDagNodes(); + + // free up the file in Maya + MFileIO::newFile( true ); +} + +/* +=============== +idMayaExport::TimeForFrame +=============== +*/ +float idMayaExport::TimeForFrame( int num ) const { + MTime time; + + // set time unit to 24 frames per second + time.setUnit( MTime::kFilm ); + time.setValue( num ); + return time.as( MTime::kSeconds ); +} + +/* +=============== +idMayaExport::GetMayaFrameNum +=============== +*/ +int idMayaExport::GetMayaFrameNum( int num ) const { + int frameNum; + + if ( options.cycleStart > options.startframe ) { + // in cycles, the last frame is a duplicate of the first frame, so with cycleStart we need to + // duplicate one of the interior frames instead and chop off the first frame. + frameNum = options.cycleStart + num; + if ( frameNum > options.endframe ) { + frameNum -= options.endframe - options.startframe; + } + if ( frameNum < options.startframe ) { + frameNum = options.startframe + 1; + } + } else { + frameNum = options.startframe + num; + if ( frameNum > options.endframe ) { + frameNum -= options.endframe + 1 - options.startframe; + } + if ( frameNum < options.startframe ) { + frameNum = options.startframe; + } + } + + return frameNum; +} + +/* +=============== +idMayaExport::SetFrame +=============== +*/ +void idMayaExport::SetFrame( int num ) { + MTime time; + int frameNum; + + frameNum = GetMayaFrameNum( num ); + + // set time unit to 24 frames per second + time.setUnit( MTime::kFilm ); + time.setValue( frameNum ); + MGlobal::viewFrame( time ); +} + +/* +=============== +idMayaExport::PruneJoints +=============== +*/ +void idMayaExport::PruneJoints( idStrList &keepjoints, idStr &prefix ) { + int i; + int j; + idExportMesh *mesh; + idExportJoint *joint; + idExportJoint *joint2; + idExportJoint *parent; + int num_weights; + + // if we don't have any joints specified to keep, mark the ones used by the meshes as keep + if ( !keepjoints.Num() && !prefix.Length() ) { + if ( !model.meshes.Num() || options.ignoreMeshes ) { + // export all joints + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + joint->keep = true; + } + } else { + for( i = 0; i < model.meshes.Num(); i++, mesh++ ) { + mesh = model.meshes[ i ]; + for( j = 0; j < mesh->weights.Num(); j++ ) { + mesh->weights[ j ].joint->keep = true; + } + } + } + } else { + // mark the joints to keep + for( i = 0; i < keepjoints.Num(); i++ ) { + joint = model.FindJoint( keepjoints[ i ] ); + if ( joint ) { + joint->keep = true; + } + } + + // count valid meshes + for( i = 0; i < model.meshes.Num(); i++ ) { + mesh = model.meshes[ i ]; + num_weights = 0; + for( j = 0; j < mesh->weights.Num(); j++ ) { + if ( mesh->weights[ j ].joint->keep ) { + num_weights++; + } else if ( prefix.Length() && !mesh->weights[ j ].joint->realname.Cmpn( prefix, prefix.Length() ) ) { + // keep the joint if it's used by the mesh and it has the right prefix + mesh->weights[ j ].joint->keep = true; + num_weights++; + } + } + + if ( num_weights != mesh->weights.Num() ) { + mesh->keep = false; + } + } + } + + // find all joints aren't exported and reparent joint's children + model.export_joints = 0; + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + if ( !joint->keep ) { + joint->exportNode.RemoveFromHierarchy(); + } else { + joint->index = model.export_joints; + model.export_joints++; + + // make sure we are parented to an exported joint + for( parent = joint->exportNode.GetParent(); parent != NULL; parent = parent->exportNode.GetParent() ) { + if ( parent->keep ) { + break; + } + } + + if ( parent != NULL ) { + joint->exportNode.ParentTo( parent->exportNode ); + } else { + joint->exportNode.ParentTo( model.exportHead ); + } + } + } + + // check if we have any duplicate joint names + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + if ( !joint->keep ) { + MayaError( "Non-kept joint in export tree ('%s')", joint->name.c_str() ); + } + + for( joint2 = model.exportHead.GetNext(); joint2 != NULL; joint2 = joint2->exportNode.GetNext() ) { + if ( ( joint2 != joint ) && ( joint2->name == joint->name ) ) { + MayaError( "Two joints found with the same name ('%s')", joint->name.c_str() ); + } + } + } +} + +/* +=============== +idMayaExport::FreeDagNodes +=============== +*/ +void idMayaExport::FreeDagNodes( void ) { + int i; + + for( i = 0; i < model.joints.Num(); i++ ) { + delete model.joints[ i ].dagnode; + model.joints[ i ].dagnode = NULL; + } +} + +/* +=============== +idMayaExport::GetBindPose +=============== +*/ +void idMayaExport::GetBindPose( MObject &jointNode, idExportJoint *joint, float scale ) { + MStatus status; + MFnDependencyNode fnJoint( jointNode ); + MObject aBindPose = fnJoint.attribute( "bindPose", &status ); + + joint->bindpos = vec3_zero; + joint->bindmat = mat3_default; + + if ( MS::kSuccess == status ) { + unsigned ii; + unsigned jointIndex; + unsigned connLength; + MPlugArray connPlugs; + MPlug pBindPose( jointNode, aBindPose ); + + pBindPose.connectedTo( connPlugs, false, true ); + connLength = connPlugs.length(); + for( ii = 0; ii < connLength; ++ii ) { + if ( connPlugs[ ii ].node().apiType() == MFn::kDagPose ) { + MObject aMember = connPlugs[ ii ].attribute(); + MFnAttribute fnAttr( aMember ); + + if ( fnAttr.name() == "worldMatrix" ) { + jointIndex = connPlugs[ ii ].logicalIndex(); + + MFnDependencyNode nDagPose( connPlugs[ ii ].node() ); + + // construct plugs for this joint's world matrix + MObject aWorldMatrix = nDagPose.attribute( "worldMatrix" ); + MPlug pWorldMatrix( connPlugs[ ii ].node(), aWorldMatrix ); + + pWorldMatrix.selectAncestorLogicalIndex( jointIndex, aWorldMatrix ); + + // get the world matrix data + MObject worldMatrix; + MStatus status = pWorldMatrix.getValue( worldMatrix ); + if ( MS::kSuccess != status ) { + // Problem retrieving world matrix + return; + } + + MFnMatrixData dMatrix( worldMatrix ); + MMatrix wMatrix = dMatrix.matrix( &status ); + + joint->bindmat = ConvertToIdSpace( idMat( wMatrix ) ); + joint->bindpos = ConvertToIdSpace( idVec( wMatrix ) ) * scale; + if ( !options.ignoreScale ) { + joint->bindpos *= joint->scale; + } else { + joint->bindmat[ 0 ].Normalize(); + joint->bindmat[ 1 ].Normalize(); + joint->bindmat[ 2 ].Normalize(); + } + + return; + } + } + } + } +} + +/* +=============== +idMayaExport::GetLocalTransform +=============== +*/ +void idMayaExport::GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat ) { + MStatus status; + MDagPath dagPath; + + pos.Zero(); + mat.Identity(); + + if ( !joint->dagnode ) { + return; + } + + status = joint->dagnode->getPath( dagPath ); + if ( !status ) { + return; + } + + MObject transformNode = dagPath.transform( &status ); + if ( !status && ( status.statusCode () == MStatus::kInvalidParameter ) ) { + return; + } + + MFnDagNode transform( transformNode, &status ); + if ( !status ) { + return; + } + + pos = idVec( transform.transformationMatrix() ); + mat = idMat( transform.transformationMatrix() ); +} + +/* +=============== +idMayaExport::GetWorldTransform +=============== +*/ +void idMayaExport::GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale ) { + idExportJoint *parent; + + GetLocalTransform( joint, pos, mat ); + mat.OrthoNormalizeSelf(); + pos *= scale; + + parent = joint->mayaNode.GetParent(); + if ( parent ) { + idVec3 parentpos; + idMat3 parentmat; + + GetWorldTransform( parent, parentpos, parentmat, scale ); + + pos = parentpos + ( parentmat * ( pos * parent->scale ) ); + mat = mat * parentmat; + } +} + +/* +=============== +idMayaExport::CreateJoints +=============== +*/ +void idMayaExport::CreateJoints( float scale ) { + int i; + int j; + idExportJoint *joint; + idExportJoint *parent; + MStatus status; + MDagPath dagPath; + MFnDagNode *parentNode; + + SetFrame( 0 ); + + // create an initial list with all of the transformable nodes in the scene + MItDag dagIterator( MItDag::kDepthFirst, MFn::kTransform, &status ); + for ( ; !dagIterator.isDone(); dagIterator.next() ) { + status = dagIterator.getPath( dagPath ); + if ( !status ) { + MayaError( "CreateJoints: MItDag::getPath failed (%s)", status.errorString().asChar() ); + continue; + } + + joint = &model.joints.Alloc(); + joint->index = model.joints.Num() - 1; + joint->dagnode = new MFnDagNode( dagPath, &status ); + if ( !status ) { + MayaError( "CreateJoints: MFnDagNode constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + joint->name = joint->dagnode->name().asChar(); + joint->realname = joint->name; + } + + // allocate an extra joint in case we need to add an origin later + model.exportOrigin = &model.joints.Alloc(); + model.exportOrigin->index = model.joints.Num() - 1; + + // create scene hierarchy + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + if ( !joint->dagnode ) { + continue; + } + joint->mayaNode.ParentTo( model.mayaHead ); + joint->exportNode.ParentTo( model.exportHead ); + + parentNode = GetParent( joint->dagnode ); + if ( parentNode ) { + // find the parent joint in our jointlist + for( j = 0; j < model.joints.Num(); j++ ) { + if ( !model.joints[ j ].dagnode ) { + continue; + } + if ( model.joints[ j ].dagnode->name() == parentNode->name() ) { + joint->mayaNode.ParentTo( model.joints[ j ].mayaNode ); + joint->exportNode.ParentTo( model.joints[ j ].exportNode ); + break; + } + } + + delete parentNode; + } + + // create long name + parent = joint->mayaNode.GetParent(); + if ( parent ) { + joint->longname = parent->longname + "/" + joint->name; + } else { + joint->longname = joint->name; + } + + // get the joint's scale + GetLocalTransform( &model.joints[ i ], joint->t, joint->wm ); + joint->scale = joint->wm[ 0 ].Length(); + + if ( parent ) { + joint->scale *= parent->scale; + if ( joint->scale != 0 ) { + joint->invscale = 1.0f / joint->scale; + } else { + joint->invscale = 0; + } + } + + joint->dagnode->getPath( dagPath ); + GetBindPose( dagPath.node( &status ), joint, scale ); + } +} + +/* +=============== +idMayaExport::RenameJoints +=============== +*/ +void idMayaExport::RenameJoints( idList &renamejoints, idStr &prefix ) { + int i; + idExportJoint *joint; + + // rename joints that match the prefix + if ( prefix.Length() ) { + joint = model.joints.Ptr(); + for( i = 0; i < model.joints.Num(); i++, joint++ ) { + if ( !joint->name.Cmpn( prefix, prefix.Length() ) ) { + // remove the prefix from the name + joint->name = joint->name.Right( joint->name.Length() - prefix.Length() ); + } + } + } + + // rename joints if necessary + for( i = 0; i < renamejoints.Num(); i++ ) { + joint = model.FindJoint( renamejoints[ i ].from ); + if ( joint ) { + joint->name = renamejoints[ i ].to; + } + } +} + +/* +=============== +idMayaExport::RemapParents +=============== +*/ +bool idMayaExport::RemapParents( idList &remapjoints ) { + int i; + idExportJoint *joint; + idExportJoint *parent; + idExportJoint *origin; + idExportJoint *sibling; + + for( i = 0; i < remapjoints.Num(); i++ ) { + // find joint to reparent + joint = model.FindJoint( remapjoints[ i ].from ); + if ( !joint ) { + // couldn't find joint, fail + MayaError( "Couldn't find joint '%s' to reparent\n", remapjoints[ i ].from.c_str() ); + } + + // find new parent joint + parent = model.FindJoint( remapjoints[ i ].to ); + if ( !parent ) { + // couldn't find parent, fail + MayaError( "Couldn't find joint '%s' to be new parent for '%s'\n", remapjoints[ i ].to.c_str(), remapjoints[ i ].from.c_str() ); + } + + if ( parent->exportNode.ParentedBy( joint->exportNode ) ) { + MayaError( "Joint '%s' is a child of joint '%s' and can't become the parent.", joint->name.c_str(), parent->name.c_str() ); + } + joint->exportNode.ParentTo( parent->exportNode ); + } + + // if we have an origin, make it the first node in the export list, otherwise add one + origin = model.FindJoint( "origin" ); + if ( !origin ) { + origin = model.exportOrigin; + origin->dagnode = NULL; + origin->name = "origin"; + origin->realname = "origin"; + origin->bindmat.Identity(); + origin->bindpos.Zero(); + } + + origin->exportNode.ParentTo( model.exportHead ); + + // force the joint to be kept + origin->keep = true; + + // make all root joints children of the origin joint + joint = model.exportHead.GetChild(); + while( joint ) { + sibling = joint->exportNode.GetSibling(); + if ( joint != origin ) { + joint->exportNode.ParentTo( origin->exportNode ); + } + joint = sibling; + } + + return true; +} + +/* +=============== +idMayaExport::FindShader + +Find the shading node for the given shading group set node. +=============== +*/ +MObject idMayaExport::FindShader( MObject& setNode ) { + MStatus status; + MFnDependencyNode fnNode( setNode ); + MPlug shaderPlug; + + shaderPlug = fnNode.findPlug( "surfaceShader" ); + if ( !shaderPlug.isNull() ) { + MPlugArray connectedPlugs; + bool asSrc = false; + bool asDst = true; + shaderPlug.connectedTo( connectedPlugs, asDst, asSrc, &status ); + + if ( connectedPlugs.length() != 1 ) { + MayaError( "FindShader: Error getting shader (%s)", status.errorString().asChar() ); + } else { + return connectedPlugs[ 0 ].node(); + } + } + + return MObject::kNullObj; +} + +/* +=============== +idMayaExport::GetTextureForMesh + +Find the texture files that apply to the color of each polygon of +a selected shape if the shape has its polygons organized into sets. +=============== +*/ +void idMayaExport::GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode ) { + MStatus status; + MDagPath path; + int i; + int instanceNum; + + status = dagNode.getPath( path ); + if ( !status ) { + return; + } + + path.extendToShape(); + + // If the shape is instanced then we need to determine which + // instance this path refers to. + // + instanceNum = 0; + if ( path.isInstanced() ) { + instanceNum = path.instanceNumber(); + } + + // Get a list of all sets pertaining to the selected shape and the + // members of those sets. + // + MFnMesh fnMesh( path ); + MObjectArray sets; + MObjectArray comps; + status = fnMesh.getConnectedSetsAndMembers( instanceNum, sets, comps, true ); + if ( !status ) { + MayaError( "GetTextureForMesh: MFnMesh::getConnectedSetsAndMembers failed (%s)", status.errorString().asChar() ); + } + + // Loop through all the sets. If the set is a polygonal set, find the + // shader attached to the and print out the texture file name for the + // set along with the polygons in the set. + // + for ( i = 0; i < ( int )sets.length(); i++ ) { + MObject set = sets[i]; + MObject comp = comps[i]; + + MFnSet fnSet( set, &status ); + if ( status == MS::kFailure ) { + MayaError( "GetTextureForMesh: MFnSet constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + // Make sure the set is a polygonal set. If not, continue. + MItMeshPolygon piter(path, comp, &status); + if (status == MS::kFailure) { + continue; + } + + // Find the texture that is applied to this set. First, get the + // shading node connected to the set. Then, if there is an input + // attribute called "color", search upstream from it for a texture + // file node. + // + MObject shaderNode = FindShader( set ); + if ( shaderNode == MObject::kNullObj ) { + continue; + } + + MPlug colorPlug = MFnDependencyNode(shaderNode).findPlug("color", &status); + if ( status == MS::kFailure ) { + continue; + } + + MItDependencyGraph dgIt(colorPlug, MFn::kFileTexture, + MItDependencyGraph::kUpstream, + MItDependencyGraph::kBreadthFirst, + MItDependencyGraph::kNodeLevel, + &status); + + if ( status == MS::kFailure ) { + continue; + } + + dgIt.disablePruningOnFilter(); + + // If no texture file node was found, just continue. + // + if ( dgIt.isDone() ) { + continue; + } + + // Print out the texture node name and texture file that it references. + // + MObject textureNode = dgIt.thisNode(); + MPlug filenamePlug = MFnDependencyNode( textureNode ).findPlug( "fileTextureName" ); + MString textureName; + filenamePlug.getValue( textureName ); + + // remove the OS path and save it in the mesh + OSPathToRelativePath( textureName.asChar(), mesh->shader, options.game ); + mesh->shader.StripFileExtension(); + + return; + } +} + +/* +=============== +idMayaExport::CopyMesh +=============== +*/ +idExportMesh *idMayaExport::CopyMesh( MFnSkinCluster &skinCluster, float scale ) { + MStatus status; + MObjectArray objarray; + MObjectArray outputarray; + int nGeom; + int i, j, k; + idExportMesh *mesh; + float uv_u, uv_v; + idStr name, altname; + int pos; + + status = skinCluster.getInputGeometry( objarray ); + if ( !status ) { + MayaError( "CopyMesh: Error getting input geometry (%s)", status.errorString().asChar() ); + return NULL; + } + + nGeom = objarray.length(); + for( i = 0; i < nGeom; i++ ) { + MFnDagNode dagNode( objarray[ i ], &status ); + if ( !status ) { + common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + MFnMesh fnmesh( objarray[ i ], &status ); + if ( !status ) { + // object isn't an MFnMesh + continue; + } + + status = skinCluster.getOutputGeometry( outputarray ); + if ( !status ) { + common->Printf( "CopyMesh: Error getting output geometry (%s)", status.errorString().asChar() ); + return NULL; + } + + if ( outputarray.length() < 1 ) { + return NULL; + } + + name = fnmesh.name().asChar(); + if ( options.prefix.Length() ) { + if ( !name.Cmpn( options.prefix, options.prefix.Length() ) ) { + // remove the prefix from the name + name = name.Right( name.Length() - options.prefix.Length() ); + } else { + // name doesn't match prefix, so don't use this mesh + //return NULL; + } + } + + pos = name.Find( "ShapeOrig" ); + if ( pos >= 0 ) { + name.CapLength( pos ); + } + + MFnDagNode dagNode2( outputarray[ 0 ], &status ); + if ( !status ) { + common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() ); + continue; + } + + altname = name; + MObject parent = fnmesh.parent( 0, &status ); + if ( status ) { + MFnDagNode parentNode( parent, &status ); + if ( status ) { + altname = parentNode.name().asChar(); + } + } + + name.StripLeadingOnce( options.prefix ); + altname.StripLeadingOnce( options.prefix ); + if ( options.keepmeshes.Num() ) { + if ( !options.keepmeshes.Find( name ) && !options.keepmeshes.Find( altname ) ) { + if ( altname != name ) { + common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() ); + } else { + common->Printf( "Skipping mesh '%s'\n", name.c_str() ); + } + return NULL; + } + } + + if ( options.skipmeshes.Find( name ) || options.skipmeshes.Find( altname ) ) { + common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() ); + return NULL; + } + + mesh = new idExportMesh(); + model.meshes.Append( mesh ); + + if ( altname.Length() ) { + mesh->name = altname; + } else { + mesh->name = name; + } + GetTextureForMesh( mesh, dagNode2 ); + + int v = fnmesh.numVertices( &status ); + mesh->verts.SetNum( v ); + + MFloatPointArray vertexArray; + + fnmesh.getPoints( vertexArray, MSpace::kPreTransform ); + + for( j = 0; j < v; j++ ) { + memset( &mesh->verts[ j ], 0, sizeof( mesh->verts[ j ] ) ); + mesh->verts[ j ].pos = ConvertToIdSpace( idVec( vertexArray[ j ] ) ) * scale; + } + + MIntArray vertexList; + int p; + + p = fnmesh.numPolygons( &status ); + mesh->tris.SetNum( p ); + mesh->uv.SetNum( p ); + + MString setName; + + status = fnmesh.getCurrentUVSetName( setName ); + if ( !status ) { + MayaError( "CopyMesh: MFnMesh::getCurrentUVSetName failed (%s)", status.errorString().asChar() ); + } + + for( j = 0; j < p; j++ ) { + fnmesh.getPolygonVertices( j, vertexList ); + if ( vertexList.length() != 3 ) { + MayaError( "CopyMesh: Too many vertices on a face (%d)\n", vertexList.length() ); + } + + for( k = 0; k < 3; k++ ) { + mesh->tris[ j ].indexes[ k ] = vertexList[ k ]; + + status = fnmesh.getPolygonUV( j, k, uv_u, uv_v, &setName ); + if ( !status ) { + MayaError( "CopyMesh: MFnMesh::getPolygonUV failed (%s)", status.errorString().asChar() ); + } + + mesh->uv[ j ].uv[ k ][ 0 ] = uv_u; + mesh->uv[ j ].uv[ k ][ 1 ] = uv_v; + } + } + + return mesh; + } + + return NULL; +} + +/* +=============== +idMayaExport::CreateMesh +=============== +*/ +void idMayaExport::CreateMesh( float scale ) { + size_t count; + idExportMesh *mesh; + MStatus status; + exportWeight_t weight; + unsigned int nGeoms; + + // Iterate through graph and search for skinCluster nodes + MItDependencyNodes iter( MFn::kSkinClusterFilter ); + count = 0; + for ( ; !iter.isDone(); iter.next() ) { + MObject object = iter.item(); + + count++; + + // For each skinCluster node, get the list of influence objects + MFnSkinCluster skinCluster( object, &status ); + if ( !status ) { + MayaError( "%s: Error getting skin cluster (%s)", object.apiTypeStr(), status.errorString().asChar() ); + } + + mesh = CopyMesh( skinCluster, scale ); + if ( !mesh ) { + continue; + } + + MDagPathArray infs; + unsigned int nInfs = skinCluster.influenceObjects(infs, &status); + if ( !status ) { + MayaError( "Mesh '%s': Error getting influence objects (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + if ( 0 == nInfs ) { + MayaError( "Mesh '%s': No influence objects found", mesh->name.c_str() ); + } + + // loop through the geometries affected by this cluster + nGeoms = skinCluster.numOutputConnections(); + for (size_t ii = 0; ii < nGeoms; ++ii) { + unsigned int index = skinCluster.indexForOutputConnection(ii,&status); + + if ( !status ) { + MayaError( "Mesh '%s': Error getting geometry index (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + // get the dag path of the ii'th geometry + MDagPath skinPath; + status = skinCluster.getPathAtIndex(index,skinPath); + if ( !status ) { + MayaError( "Mesh '%s': Error getting geometry path (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + // iterate through the components of this geometry + MItGeometry gIter( skinPath ); + + // print out the influence objects + idList joints; + idExportJoint *joint; + exportVertex_t *vert; + + joints.Resize( nInfs ); + for (size_t kk = 0; kk < nInfs; ++kk) { + const char *c; + MString s; + + s = infs[kk].partialPathName(); + c = s.asChar(); + joint = model.FindJointReal( c ); + if ( !joint ) { + MayaError( "Mesh '%s': joint %s not found", mesh->name.c_str(), c ); + } + + joints.Append( joint ); + } + + for ( /* nothing */ ; !gIter.isDone(); gIter.next() ) { + MObject comp = gIter.component( &status ); + if ( !status ) { + MayaError( "Mesh '%s': Error getting component (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + + // Get the weights for this vertex (one per influence object) + MFloatArray wts; + unsigned infCount; + status = skinCluster.getWeights(skinPath,comp,wts,infCount); + if ( !status ) { + MayaError( "Mesh '%s': Error getting weights (%s)", mesh->name.c_str(), status.errorString().asChar() ); + } + if (0 == infCount) { + MayaError( "Mesh '%s': Error: 0 influence objects.", mesh->name.c_str() ); + } + + int num = gIter.index(); + vert = &mesh->verts[ num ]; + vert->startweight = mesh->weights.Num(); + + float totalweight = 0.0f; + + // copy the weight data for this vertex + int numNonZeroWeights = 0; + int jj; + for ( jj = 0; jj < (int)infCount ; ++jj ) { + float w = ( float )wts[ jj ]; + if ( w > 0.0f ) { + numNonZeroWeights++; + } + if ( w > options.jointThreshold ) { + weight.joint = joints[ jj ]; + weight.jointWeight = wts[ jj ]; + + if ( !options.ignoreScale ) { + weight.joint->bindmat.ProjectVector( vert->pos - ( weight.joint->bindpos * weight.joint->invscale ), weight.offset ); + weight.offset *= weight.joint->scale; + } else { + weight.joint->bindmat.ProjectVector( vert->pos - weight.joint->bindpos, weight.offset ); + } + mesh->weights.Append( weight ); + totalweight += weight.jointWeight; + } + } + + vert->numWeights = mesh->weights.Num() - vert->startweight; + if ( !vert->numWeights ) { + if ( numNonZeroWeights ) { + MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights exceeding jointThreshold (%f).", mesh->name.c_str(), num, options.jointThreshold ); + } else { + MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights.", mesh->name.c_str(), num ); + } + } else if ( !totalweight ) { + MayaError( "Error on mesh '%s': Combined weight of 0 on vertex %d.", mesh->name.c_str(), num ); + } + //if ( numNonZeroWeights ) { + // common->Printf( "Mesh '%s': skipped %d out of %d weights on vertex %d\n", mesh->name.c_str(), numNonZeroWeights, numNonZeroWeights + vert->numWeights, num ); + //} + + // normalize the joint weights + for( jj = 0; jj < vert->numWeights; jj++ ) { + mesh->weights[ vert->startweight + jj ].jointWeight /= totalweight; + } + } + break; + } + } + + if ( !count && !options.ignoreMeshes ) { + MayaError( "CreateMesh: No skinClusters found in this scene.\n" ); + } +} + +/* +=============== +idMayaExport::CombineMeshes + +combine surfaces with the same shader. +=============== +*/ +void idMayaExport::CombineMeshes( void ) { + int i, j; + int count; + idExportMesh *mesh; + idExportMesh *combine; + idList oldmeshes; + + oldmeshes = model.meshes; + model.meshes.Clear(); + + count = 0; + for( i = 0; i < oldmeshes.Num(); i++ ) { + mesh = oldmeshes[ i ]; + if ( !mesh->keep ) { + delete mesh; + continue; + } + + combine = NULL; + for( j = 0; j < model.meshes.Num(); j++ ) { + if ( model.meshes[ j ]->shader == mesh->shader ) { + combine = model.meshes[ j ]; + break; + } + } + + if ( combine ) { + combine->Merge( mesh ); + delete mesh; + count++; + } else { + model.meshes.Append( mesh ); + } + } + + // share verts + for( i = 0; i < model.meshes.Num(); i++ ) { + model.meshes[ i ]->ShareVerts(); + } + + common->Printf( "Merged %d meshes\n", count ); +} + +/* +=============== +idMayaExport::GetAlignment +=============== +*/ +void idMayaExport::GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe ) { + idVec3 pos; + idExportJoint *joint; + idAngles ang( 0, rotate, 0 ); + idMat3 mat; + + align.Identity(); + + if ( alignName.Length() ) { + SetFrame( 0 ); + + joint = model.FindJoint( alignName ); + if ( !joint ) { + MayaError( "could not find joint '%s' to align model to.\n", alignName.c_str() ); + } + + // found it + GetWorldTransform( joint, pos, mat, 1.0f ); + align[ 0 ][ 0 ] = mat[ 2 ][ 0 ]; + align[ 0 ][ 1 ] = -mat[ 2 ][ 2 ]; + align[ 0 ][ 2 ] = mat[ 2 ][ 1 ]; + + align[ 1 ][ 0 ] = mat[ 0 ][ 0 ]; + align[ 1 ][ 1 ] = -mat[ 0 ][ 2 ]; + align[ 1 ][ 2 ] = mat[ 0 ][ 1 ]; + + align[ 2 ][ 0 ] = mat[ 1 ][ 0 ]; + align[ 2 ][ 1 ] = -mat[ 1 ][ 2 ]; + align[ 2 ][ 2 ] = mat[ 1 ][ 1 ]; + + if ( rotate ) { + align *= ang.ToMat3(); + } + } else if ( rotate ) { + align = ang.ToMat3(); + } + + align.TransposeSelf(); +} + +/* +=============== +idMayaExport::GetObjectType + +return the type of the object +=============== +*/ +const char *idMayaExport::GetObjectType( MObject object ) { + if( object.isNull() ) { + return "(Null)"; + } + + MStatus stat; + MFnDependencyNode dgNode; + MString typeName; + + stat = dgNode.setObject( object ); + typeName = dgNode.typeName( &stat ); + if( MS::kSuccess != stat ) { + // can not get the type name of this object + return "(Unknown)"; + } + + return typeName.asChar(); +} + +/* +=============== +idMayaExport::GetCameraFov +=============== +*/ +float idMayaExport::GetCameraFov( idExportJoint *joint ) { + int childCount; + int j; + double horiz; + double focal; + MStatus status; + const char *n1, *n2; + MFnDagNode *dagnode; + float fov; + + dagnode = joint->dagnode; + + MObject cameraNode = dagnode->object(); + childCount = dagnode->childCount(); + + fov = 90.0f; + for( j = 0; j < childCount; j++ ) { + MObject childNode = dagnode->child( j ); + + n1 = GetObjectType( cameraNode ); + n2 = GetObjectType( childNode ); + if ( ( !strcmp( "transform", n1 ) ) && ( !strcmp( "camera", n2 ) ) ) { + MFnCamera camera( childNode ); + focal = camera.focalLength(); + horiz = camera.horizontalFilmAperture(); + fov = RAD2DEG( 2 * atan( ( horiz * 0.5 ) / ( focal / 25.4 ) ) ); + break; + } + } + + return fov; +} + +/* +=============== +idMayaExport::GetCameraFrame +=============== +*/ +void idMayaExport::GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam ) { + idMat3 mat; + idMat3 axis; + idVec3 pos; + + // get the worldspace positions of the joint + GetWorldTransform( camera, pos, axis, 1.0f ); + + // convert to id coordinates + cam->t = ConvertToIdSpace( pos ) * align; + + // correct the orientation for the game + axis = ConvertToIdSpace( axis ) * align; + mat[ 0 ] = -axis[ 2 ]; + mat[ 1 ] = -axis[ 0 ]; + mat[ 2 ] = axis[ 1 ]; + cam->q = mat.ToQuat().ToCQuat(); + + // get it's fov + cam->fov = GetCameraFov( camera ); +} + +/* +=============== +idMayaExport::CreateCameraAnim +=============== +*/ +void idMayaExport::CreateCameraAnim( idMat3 &align ) { + float start, end; + MDagPath dagPath; + int frameNum; + short v; + MStatus status; + cameraFrame_t cam; + idExportJoint *refCam; + idExportJoint *camJoint; + idStr currentCam; + idStr newCam; + MPlug plug; + MFnEnumAttribute cameraAttribute; + + start = TimeForFrame( options.startframe ); + end = TimeForFrame( options.endframe ); + +#if 0 + options.framerate = 60; + model.numFrames = ( int )( ( end - start ) * ( float )options.framerate ) + 1; + model.frameRate = options.framerate; +#else + model.numFrames = options.endframe + 1 - options.startframe; + model.frameRate = options.framerate; +#endif + + common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe ); + common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start ); + + if ( start > end ) { + MayaError( "Start frame is greater than end frame." ); + } + + refCam = model.FindJoint( "refcam" ); + if ( refCam == NULL ) { + currentCam = MAYA_DEFAULT_CAMERA; + } else { + MObject cameraNode = refCam->dagnode->object(); + MFnDependencyNode cameraDG( cameraNode, &status ); + if( MS::kSuccess != status ) { + MayaError( "Can't find 'refcam' dependency node." ); + return; + } + + MObject attr = cameraDG.attribute( MString( "Camera" ), &status ); + if( MS::kSuccess != status ) { + MayaError( "Can't find 'Camera' attribute on 'refcam'." ); + return; + } + + plug = MPlug( cameraNode, attr ); + status = cameraAttribute.setObject( attr ); + if( MS::kSuccess != status ) { + MayaError( "Bad 'Camera' attribute on 'refcam'." ); + return; + } + + model.camera.Clear(); + model.cameraCuts.Clear(); + + SetFrame( 0 ); + status = plug.getValue( v ); + currentCam = cameraAttribute.fieldName( v, &status ).asChar(); + if( MS::kSuccess != status ) { + MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( 0 ) ); + } + } + + camJoint = model.FindJoint( currentCam ); + if ( !camJoint ) { + MayaError( "Couldn't find camera '%s'", currentCam.c_str() ); + } + + for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) { + common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe ); + +#if 0 + MTime time; + time.setUnit( MTime::kSeconds ); + time.setValue( start + ( ( float )frameNum / ( float )options.framerate ) ); + MGlobal::viewFrame( time ); +#else + SetFrame( frameNum ); +#endif + + // get the position for this frame + GetCameraFrame( camJoint, align, &model.camera.Alloc() ); + + if ( refCam != NULL ) { + status = plug.getValue( v ); + newCam = cameraAttribute.fieldName( v, &status ).asChar(); + if( MS::kSuccess != status ) { + MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( frameNum ) ); + } + + if ( newCam != currentCam ) { + // place a cut at our last frame + model.cameraCuts.Append( model.camera.Num() - 1 ); + + currentCam = newCam; + camJoint = model.FindJoint( currentCam ); + if ( !camJoint ) { + MayaError( "Couldn't find camera '%s'", currentCam.c_str() ); + } + + // get the position for this frame + GetCameraFrame( camJoint, align, &model.camera.Alloc() ); + } + } + } + + common->Printf( "\n" ); +} + +/* +=============== +idMayaExport::GetDefaultPose +=============== +*/ +void idMayaExport::GetDefaultPose( idMat3 &align ) { + float start; + MDagPath dagPath; + idMat3 jointaxis; + idVec3 jointpos; + idExportJoint *joint, *parent; + idBounds bnds; + idBounds meshBounds; + idList frame; + + start = TimeForFrame( options.startframe ); + + common->Printf( "default pose frame = %d\n", options.startframe ); + common->Printf( " default pose time = %f\n", start ); + + frame.SetNum( model.joints.Num() ); + SetFrame( 0 ); + + // convert joints into local coordinates and save in channels + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + if ( !joint->dagnode ) { + // custom origin joint + joint->idwm.Identity(); + joint->idt.Zero(); + frame[ joint->index ].t.Zero(); + frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f ); + continue; + } + + // get the worldspace positions of the joint + GetWorldTransform( joint, jointpos, jointaxis, options.scale ); + + // convert to id coordinates + jointaxis = ConvertToIdSpace( jointaxis ) * align; + jointpos = ConvertToIdSpace( jointpos ) * align; + + // save worldspace position of joint for children + joint->idwm = jointaxis; + joint->idt = jointpos; + + parent = joint->exportNode.GetParent(); + if ( parent ) { + // convert to local coordinates + jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose(); + jointaxis = jointaxis * parent->idwm.Transpose(); + } else if ( joint->name == "origin" ) { + if ( options.clearOrigin ) { + jointpos.Zero(); + } + if ( options.clearOriginAxis ) { + jointaxis.Identity(); + } + } + + frame[ joint->index ].t = jointpos; + frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat(); + } + + // relocate origin to start at 0, 0, 0 for first frame + joint = model.FindJoint( "origin" ); + if ( joint ) { + frame[ joint->index ].t.Zero(); + } + + // transform the hierarchy + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointpos = frame[ joint->index ].t; + jointaxis = frame[ joint->index ].q.ToQuat().ToMat3(); + + parent = joint->exportNode.GetParent(); + if ( parent ) { + joint->idwm = jointaxis * parent->idwm; + joint->idt = parent->idt + jointpos * parent->idwm; + } else { + joint->idwm = jointaxis; + joint->idt = jointpos; + } + + joint->bindmat = joint->idwm; + joint->bindpos = joint->idt; + } + + common->Printf( "\n" ); +} + +/* +=============== +idMayaExport::CreateAnimation +=============== +*/ +void idMayaExport::CreateAnimation( idMat3 &align ) { + int i; + float start, end; + MDagPath dagPath; + idMat3 jointaxis; + idVec3 jointpos; + int frameNum; + idExportJoint *joint, *parent; + idBounds bnds; + idBounds meshBounds; + jointFrame_t *frame; + int cycleStart; + idVec3 totalDelta; + idList copyFrames; + + start = TimeForFrame( options.startframe ); + end = TimeForFrame( options.endframe ); + + model.numFrames = options.endframe + 1 - options.startframe; + model.frameRate = options.framerate; + + common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe ); + common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start ); + + if ( start > end ) { + MayaError( "Start frame is greater than end frame." ); + } + + model.bounds.SetNum( model.numFrames ); + model.jointFrames.SetNum( model.numFrames * model.joints.Num() ); + model.frames.SetNum( model.numFrames ); + for( i = 0; i < model.numFrames; i++ ) { + model.frames[ i ] = &model.jointFrames[ model.joints.Num() * i ]; + } + + // *sigh*. cyclestart doesn't work nicely with the anims. + // may just want to not do it in SetTime anymore. + cycleStart = options.cycleStart; + options.cycleStart = options.startframe; + + for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) { + common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe ); + + frame = model.frames[ frameNum ]; + SetFrame( frameNum ); + + // convert joints into local coordinates and save in channels + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + if ( !joint->dagnode ) { + // custom origin joint + joint->idwm.Identity(); + joint->idt.Zero(); + frame[ joint->index ].t.Zero(); + frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f ); + continue; + } + + // get the worldspace positions of the joint + GetWorldTransform( joint, jointpos, jointaxis, options.scale ); + + // convert to id coordinates + jointaxis = ConvertToIdSpace( jointaxis ) * align; + jointpos = ConvertToIdSpace( jointpos ) * align; + + // save worldspace position of joint for children + joint->idwm = jointaxis; + joint->idt = jointpos; + + parent = joint->exportNode.GetParent(); + if ( parent ) { + // convert to local coordinates + jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose(); + jointaxis = jointaxis * parent->idwm.Transpose(); + } else if ( joint->name == "origin" ) { + if ( options.clearOrigin ) { + jointpos.Zero(); + } + if ( options.clearOriginAxis ) { + jointaxis.Identity(); + } + } + + frame[ joint->index ].t = jointpos; + frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat(); + } + } + + options.cycleStart = cycleStart; + totalDelta.Zero(); + + joint = model.FindJoint( "origin" ); + if ( joint ) { + frame = model.frames[ 0 ]; + idVec3 origin = frame[ joint->index ].t; + + frame = model.frames[ model.numFrames - 1 ]; + totalDelta = frame[ joint->index ].t - origin; + } + + // shift the frames when cycleStart is used + if ( options.cycleStart > options.startframe ) { + copyFrames = model.jointFrames; + for( i = 0; i < model.numFrames; i++ ) { + bool shiftorigin = false; + frameNum = i + ( options.cycleStart - options.startframe ); + if ( frameNum >= model.numFrames ) { + // wrap around, skipping the first frame, since it's a dupe of the last frame + frameNum -= model.numFrames - 1; + shiftorigin = true; + } + + memcpy( &model.jointFrames[ model.joints.Num() * i ], ©Frames[ model.joints.Num() * frameNum ], model.joints.Num() * sizeof( copyFrames[ 0 ] ) ); + + if ( joint && shiftorigin ) { + model.frames[ i ][ joint->index ].t += totalDelta; + } + } + } + + if ( joint ) { + // relocate origin to start at 0, 0, 0 for first frame + frame = model.frames[ 0 ]; + idVec3 origin = frame[ joint->index ].t; + for( i = 0; i < model.numFrames; i++ ) { + frame = model.frames[ i ]; + frame[ joint->index ].t -= origin; + } + } + + // get the bounds for each frame + for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) { + frame = model.frames[ frameNum ]; + + // transform the hierarchy + for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) { + jointpos = frame[ joint->index ].t; + jointaxis = frame[ joint->index ].q.ToQuat().ToMat3(); + + parent = joint->exportNode.GetParent(); + if ( parent ) { + joint->idwm = jointaxis * parent->idwm; + joint->idt = parent->idt + jointpos * parent->idwm; + } else { + joint->idwm = jointaxis; + joint->idt = jointpos; + } + } + + // get bounds for this frame + bnds.Clear(); + for( i = 0; i < model.meshes.Num(); i++ ) { + if ( model.meshes[ i ]->keep ) { + model.meshes[ i ]->GetBounds( meshBounds ); + bnds.AddBounds( meshBounds ); + } + } + model.bounds[ frameNum ][ 0 ] = bnds[ 0 ]; + model.bounds[ frameNum ][ 1 ] = bnds[ 1 ]; + } + + common->Printf( "\n" ); +} + +/* +=============== +idMayaExport::ConvertModel +=============== +*/ +void idMayaExport::ConvertModel( void ) { + MStatus status; + idMat3 align; + + common->Printf( "Converting %s to %s...\n", options.src.c_str(), options.dest.c_str() ); + + // see if the destination file exists + FILE *file = fopen( options.dest, "r" ); + if ( file ) { + fclose( file ); + + // make sure we can write to the destination + FILE *file = fopen( options.dest, "r+" ); + if ( !file ) { + MayaError( "Unable to write to the file '%s'", options.dest.c_str() ); + } + fclose( file ); + } + + MString filename( options.src ); + MFileIO::newFile( true ); + + // Load the file into Maya + common->Printf( "Loading file...\n" ); + status = MFileIO::open( filename, NULL, true ); + if ( !status ) { + MayaError( "Error loading '%s': '%s'\n", filename.asChar(), status.errorString().asChar() ); + } + + // force Maya to update the frame. When using references, sometimes + // the model is posed the way it is in the source. Since Maya only + // updates it when the frame time changes to a value other than the + // current, just setting the time to the min time doesn't guarantee + // that the model gets updated. + MGlobal::viewFrame( MAnimControl::maxTime() ); + MGlobal::viewFrame( MAnimControl::minTime() ); + + if ( options.startframe < 0 ) { + options.startframe = MAnimControl::minTime().as( MTime::kFilm ); + } + + if ( options.endframe < 0 ) { + options.endframe = MAnimControl::maxTime().as( MTime::kFilm ); + } + if ( options.cycleStart < 0 ) { + options.cycleStart = options.startframe; + } else if ( ( options.cycleStart < options.startframe ) || ( options.cycleStart > options.endframe ) ) { + MayaError( "cycleStart (%d) out of frame range (%d to %d)\n", options.cycleStart, options.startframe, options.endframe ); + } else if ( options.cycleStart == options.endframe ) { + // end frame is a duplicate of the first frame in cycles, so just disable cycleStart + options.cycleStart = options.startframe; + } + + // create a list of the transform nodes that will make up our heirarchy + common->Printf( "Creating joints...\n" ); + CreateJoints( options.scale ); + if ( options.type != WRITE_CAMERA ) { + common->Printf( "Creating meshes...\n" ); + CreateMesh( options.scale ); + common->Printf( "Renaming joints...\n" ); + RenameJoints( options.renamejoints, options.prefix ); + common->Printf( "Remapping parents...\n" ); + RemapParents( options.remapjoints ); + common->Printf( "Pruning joints...\n" ); + PruneJoints( options.keepjoints, options.prefix ); + common->Printf( "Combining meshes...\n" ); + CombineMeshes(); + } + + common->Printf( "Align model...\n" ); + GetAlignment( options.align, align, options.rotate, 0 ); + + switch( options.type ) { + case WRITE_MESH : + common->Printf( "Grabbing default pose:\n" ); + GetDefaultPose( align ); + common->Printf( "Writing file...\n" ); + if ( !model.WriteMesh( options.dest, options ) ) { + MayaError( "error writing to '%s'", options.dest.c_str() ); + } + break; + + case WRITE_ANIM : + common->Printf( "Creating animation frames:\n" ); + CreateAnimation( align ); + common->Printf( "Writing file...\n" ); + if ( !model.WriteAnim( options.dest, options ) ) { + MayaError( "error writing to '%s'", options.dest.c_str() ); + } + break; + + case WRITE_CAMERA : + common->Printf( "Creating camera frames:\n" ); + CreateCameraAnim( align ); + + common->Printf( "Writing file...\n" ); + if ( !model.WriteCamera( options.dest, options ) ) { + MayaError( "error writing to '%s'", options.dest.c_str() ); + } + break; + } + + common->Printf( "done\n\n" ); +} + +/* +=============== +idMayaExport::ConvertToMD3 +=============== +*/ +void idMayaExport::ConvertToMD3( void ) { +#if 0 + 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; + int version; + int size; + + //model_t *mod, int lod, void *buffer, const char *mod_name + + pinmodel = (md3Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD3_VERSION) { + common->Printf( "R_LoadMD3: %s has wrong version (%i should be %i)\n", + mod_name, version, MD3_VERSION); + return qfalse; + } + + mod->type = MOD_MESH; + size = LittleLong(pinmodel->ofsEnd); + mod->dataSize += size; + mod->md3[lod] = ri.Hunk_Alloc( size ); + + memcpy (mod->md3[lod], buffer, LittleLong(pinmodel->ofsEnd) ); + + LL(mod->md3[lod]->ident); + LL(mod->md3[lod]->version); + LL(mod->md3[lod]->numFrames); + LL(mod->md3[lod]->numTags); + LL(mod->md3[lod]->numSurfaces); + LL(mod->md3[lod]->ofsFrames); + LL(mod->md3[lod]->ofsTags); + LL(mod->md3[lod]->ofsSurfaces); + LL(mod->md3[lod]->ofsEnd); + + if ( mod->md3[lod]->numFrames < 1 ) { + common->Printf( "R_LoadMD3: %s has no frames\n", mod_name ); + return qfalse; + } + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames ); + for ( i = 0 ; i < mod->md3[lod]->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 *)mod->md3[lod] + mod->md3[lod]->ofsTags ); + for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->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 *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->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 ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MD3; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // 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++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + } + + // 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 ); + } + return true; +#endif +} + +/* +============================================================================== + +dll setup + +============================================================================== +*/ + +/* +=============== +Maya_Shutdown +=============== +*/ +void Maya_Shutdown( void ) { + if ( initialized ) { + errorMessage.Clear(); + initialized = false; + + // This shuts down the entire app somehow, so just ignore it. + //MLibrary::cleanup(); + } +} + +/* +=============== +Maya_ConvertModel +=============== +*/ +const char *Maya_ConvertModel( const char *ospath, const char *commandline ) { + + errorMessage = "Ok"; + + try { + idExportOptions options( commandline, ospath ); + idMayaExport export( options ); + + export.ConvertModel(); + } + + catch( idException &exception ) { + errorMessage = exception.error; + } + + return errorMessage; +} + +/* +=============== +dllEntry +=============== +*/ +bool dllEntry( int version, idCommon *common, idSys *sys ) { + + if ( !common || !sys ) { + return false; + } + + ::common = common; + ::sys = sys; + ::cvarSystem = NULL; + + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = NULL; + idLib::fileSystem = NULL; + + idLib::Init(); + + if ( version != MD5_VERSION ) { + common->Printf( "Error initializing Maya exporter: DLL version %d different from .exe version %d\n", MD5_VERSION, version ); + return false; + } + + if ( !initialized ) { + MStatus status; + + status = MLibrary::initialize( GAME_NAME, true ); + if ( !status ) { + common->Printf( "Error calling MLibrary::initialize (%s)\n", status.errorString().asChar() ); + return false; + } + + initialized = true; + } + + return true; +} + +// Force type checking on the interface functions to help ensure that they match the ones in the .exe +const exporterDLLEntry_t ValidateEntry = &dllEntry; +const exporterInterface_t ValidateConvert = &Maya_ConvertModel; +const exporterShutdown_t ValidateShutdown = &Maya_Shutdown; diff --git a/neo/prey/MayaImport/maya_main.h b/neo/prey/MayaImport/maya_main.h new file mode 100644 index 00000000..df375dc3 --- /dev/null +++ b/neo/prey/MayaImport/maya_main.h @@ -0,0 +1,18 @@ + +#ifndef __MAYA_MAIN_H__ +#define __MAYA_MAIN_H__ + +/* +============================================================== + + Maya Import + +============================================================== +*/ + + +typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys ); +typedef const char *( *exporterInterface_t )( const char *ospath, const char *commandline ); +typedef void ( *exporterShutdown_t )( void ); + +#endif /* !__MAYA_MAIN_H__ */ diff --git a/neo/prey/MayaImport/mayaimport.def b/neo/prey/MayaImport/mayaimport.def new file mode 100644 index 00000000..9748f40c --- /dev/null +++ b/neo/prey/MayaImport/mayaimport.def @@ -0,0 +1,4 @@ +EXPORTS + dllEntry + Maya_ConvertModel + Maya_Shutdown diff --git a/neo/prey/Prey/ai_Navigator.cpp b/neo/prey/Prey/ai_Navigator.cpp new file mode 100644 index 00000000..747a5b4e --- /dev/null +++ b/neo/prey/Prey/ai_Navigator.cpp @@ -0,0 +1,268 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + + +/* +================= +hhNavigator::hhNavigator() +================= +*/ +hhNavigator::hhNavigator(void) { + + + hhSelf = NULL; + +// followStateFunction = FollowAllyStay; + +} //. hhNavigator::hhNavigator(void) + + +/* +======================== +hhNavigator::Spawn(void) +======================== +*/ + +// Required for CLASS_DECLARATION +void hhNavigator::Spawn(void) { + + +} //. hhNavigator::hhNavigator(void) + + +/* +======================================= +hhNavigator::SetOwner(idAI *owner) +======================================= +*/ +void hhNavigator::SetOwner(idAI *owner) { + + idNavigator::SetOwner(owner); + + if (owner->IsType(hhAI::Type)) { + hhSelf = static_cast(owner); + } + +} //. hhNavigator::SetOwner(idAI *) + +/* JRM- Removed because brain stuff handles this logic now +// +//=============================== +//hhNavigator::SetAlly(idActor *) +//=============================== +// +void hhNavigator::SetAlly(idActor *ally) { + + if (ally != NULL) { + FollowAlly(ally); + } + +} //. hhNavigator::SetAlly(idActor *) + + +// +//============================= +//hhNavigator::FollowAlly(ally) +//============================= +// +void hhNavigator::FollowAlly(idActor *ally) { + + // If we aren't on an hhAI, we can't follow, so leave + if (hhSelf == NULL) { + return; + } + + + + (this->*followStateFunction)(ally); + + + +} //. hhNavigator::FollowAlly(idActor *ally) +*/ + + + + + +/* HUMANHEAD JRM - REMOVED +// +//=================================== +//hhNavigator::FollowAllyStay(void) +//=================================== +// +void hhNavigator::FollowAllyStay(idActor *ally) { + static bool first = true; + + + if (first) { + if (ai_debug->integer > 0) { + gameLocal.Printf("in FollowAllyStay\n"); + } + first = false; + } + + if (hhSelf->AI_ALLY_FAR || + !hhSelf->AI_ALLY_VISIBLE) { + followStateFunction = FollowAllyFollow; + first = true; + } + else if (hhSelf->AI_ALLY_TOUCHED) { + followStateFunction = FollowAllyLead; + first = true; + } + else { // Just stay still + moveType = MOVE_NONE; + } + + +} //. hhNavigator::FollowAllyStay(void) + + +// +//=================================== +//hhNavigator::FollowAllyFollow(void) +//=================================== +// +void hhNavigator::FollowAllyFollow(idActor *ally) { + static bool first = true; + + + hhSelf->AI_FOLLOW_ALLY = true; + + if (first) { + if (ai_debug->integer > 0) { + gameLocal.Printf("in FollowAllyFollow\n"); + } + first = false; + } + + if (hhSelf->AI_ALLY_NEAR) { + StopMove(); + + followStateFunction = FollowAllyStay; + hhSelf->AI_FOLLOW_ALLY = false; + first = true; + } + // In case the min_dist is too close + else if (hhSelf->AI_ALLY_TOUCHED) { + followStateFunction = FollowAllyLead; + hhSelf->AI_FOLLOW_ALLY = false; + first = true; + } + else { + moveDest = ally->GetFloorPos(); + goal = ally; + moveType = MOVE_TO_ALLY; + + if (aas) { + toAreaNum = aas->PointReachableAreaNum( moveDest ); + toAreaEnemy = false; + } + } //. Follow the player + +} //. hhNavigator::FollowAllyFollow(void) + + +// +//=================================== +//hhNavigator::FollowAllyLead(void) +//=================================== +// +void hhNavigator::FollowAllyLead(idActor *ally) { + static bool first = true; + static int nextUpdateTime; + idVec3 leadPosition; + + + hhSelf->AI_LEAD_ALLY = true; + + if (first) { + if (ai_debug->integer > 0) { + gameLocal.Printf("in FollowAllyLead\n"); + } + first = false; + } + + // Reset this variable, as it was used to get here. + if (hhSelf->AI_ALLY_TOUCHED) { + hhSelf->AI_ALLY_TOUCHED = false; + nextUpdateTime = 0; + } + + if (gameLocal.time * 1000.0f >= nextUpdateTime) { + //if (nextUpdateTime == 0) { + + leadPosition = FindNewLeadPosition(ally); + + if (leadPosition != vec3_origin) { + moveDest = leadPosition; + moveType = MOVE_TO_ALLY; + goal = NULL; + + if (aas) { + toAreaNum = aas->PointReachableAreaNum(moveDest); + toAreaEnemy = false; + } + + nextUpdateTime = gameLocal.time * 1000.0f + + hhSelf->follow_lead_update_time; + } + + } //. Time to update position + + if (!hhSelf->AI_ALLY_NEAR) { + followStateFunction = FollowAllyStay; + hhSelf->AI_LEAD_ALLY = false; + first = true; + } + + +} //. hhNavigator::FollowAllyLead(void) + + +// +//================================ +//hhNavigator::FindNewLeadPosition +//================================ +// +idVec3 hhNavigator::FindNewLeadPosition(idActor *ally) { + idVec3 direction; + aasTrace_t trace; + + + direction = hhSelf->GetFloorPos() - ally->GetFloorPos(); + direction.Normalize(); + direction *= hhSelf->follow_min_dist; + + if (aas) { + idVec3 bestPos; + + bestPos = aas->FindNearestPoint(ally->GetFloorPos(), + hhSelf->GetFloorPos(), + hhSelf->follow_min_dist); + if (bestPos != vec3_origin) { + return(bestPos); + } + + // Didn't find a good point + + // Just move as far as we can in the direction nudged + aas->Trace(trace, hhSelf->GetPhysics()->GetOrigin(), + hhSelf->GetPhysics()->GetOrigin() + direction); + + //? How does this work on slopes? + //! Do we want to use the floor position? + return(ally->GetFloorPos() + direction * trace.fraction); + } + + return(ally->GetFloorPos() + direction); + + + +} //. hhNavigator::FindNewLeadPosition(idActor *) +*/ diff --git a/neo/prey/Prey/ai_Navigator.h b/neo/prey/Prey/ai_Navigator.h new file mode 100644 index 00000000..4a50a367 --- /dev/null +++ b/neo/prey/Prey/ai_Navigator.h @@ -0,0 +1,41 @@ + +#ifndef __PREY_AI_NAVIGATOR_H__ +#define __PREY_AI_NAVIGATOR_H__ + +// Forward declaration for hhAI +class hhAI; + +class hhNavigator : public idNavigator { + +public: + + hhNavigator(void); + + void Spawn(void); + + //virtual void SetAlly(idActor *ally); JRM removed + + virtual void SetOwner(idAI *owner); + + virtual boolean IsNearDest( void ); + +protected: + + hhAI * hhSelf; + + //virtual void FollowAlly(idActor *ally); JRM removed + + /* JRM removed + void FollowAllyStay(idActor *ally); + void FollowAllyFollow(idActor *ally); + void FollowAllyLead(idActor *ally); + */ + +// void (hhNavigator::* followStateFunction) (idActor *ally); JRM removed + + //idVec3 FindNewLeadPosition(idActor *ally); JRM removed + +}; + + +#endif /* __PREY_AI_NAVIGATOR_H__ */ diff --git a/neo/prey/Prey/ai_centurion.cpp b/neo/prey/Prey/ai_centurion.cpp new file mode 100644 index 00000000..2fc8d403 --- /dev/null +++ b/neo/prey/Prey/ai_centurion.cpp @@ -0,0 +1,529 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef AI_CenturionLaunch("centurionFire", "d"); //called from anims to handle firing for centurion +const idEventDef AI_CenturionRoar("centurionRoar"); //called from level-script to cause centurion to roar +const idEventDef AI_CenturionArmChop("centurionArmChop"); //called from level-script to actually cause centurion to loose arm +const idEventDef AI_CenturionLooseArm("centurionLooseArm"); //internal: used to actually 'loose' arm +const idEventDef AI_CenturionForceFieldNotify("preForcefieldNotify"); +const idEventDef AI_CenturionForceFieldToggle("forcefieldToggle", "d"); +const idEventDef AI_CenturionInTunnel("playerInBox", "dE"); +const idEventDef AI_CenturionReachedTunnel("reachedTunnel"); +const idEventDef AI_CenturionMoveToTunnel("moveToTunnel", NULL, 'd'); +const idEventDef AI_CheckForObstructions("checkForObstructions", "d", 'd'); +const idEventDef AI_DestroyObstruction("destroyObstruction"); +const idEventDef AI_MoveToObstruction("moveToObstruction"); +const idEventDef AI_ReachedObstruction("reachedObstruction"); +const idEventDef AI_CloseToObstruction("closeToObstruction", 0, 'd'); +const idEventDef AI_BackhandImpulse("", "e"); +const idEventDef AI_EnemyCloseToObstruction("enemyCloseToObstruction", NULL, 'd'); +const idEventDef AI_TakeDamage("takeDamage", "d"); +const idEventDef AI_FindNearbyEnemy("findNearbyEnemy", "f", 'e'); + +CLASS_DECLARATION( hhMonsterAI, hhCenturion ) + EVENT( AI_CenturionLaunch, hhCenturion::Event_CenturionLaunch ) + EVENT( AI_CenturionRoar, hhCenturion::Event_ScriptedRoar ) + EVENT( AI_CenturionArmChop, hhCenturion::Event_ScriptedArmChop ) + EVENT( AI_CenturionLooseArm, hhCenturion::Event_CenturionLooseArm ) + EVENT( AI_CenturionInTunnel, hhCenturion::Event_PlayerInTunnel ) + EVENT( AI_CenturionReachedTunnel, hhCenturion::Event_ReachedTunnel ) + EVENT( AI_CenturionMoveToTunnel, hhCenturion::Event_MoveToTunnel ) + EVENT( AI_CenturionForceFieldNotify, hhCenturion::Event_ForceFieldNotify ) + EVENT( AI_CenturionForceFieldToggle, hhCenturion::Event_ForceFieldToggle ) + EVENT( AI_CheckForObstructions, hhCenturion::Event_CheckForObstruction ) + EVENT( AI_MoveToObstruction, hhCenturion::Event_MoveToObstruction ) + EVENT( AI_DestroyObstruction, hhCenturion::Event_DestroyObstruction ) + EVENT( AI_ReachedObstruction, hhCenturion::Event_ReachedObstruction ) + EVENT( AI_CloseToObstruction, hhCenturion::Event_CloseToObstruction ) + EVENT( AI_BackhandImpulse, hhCenturion::Event_BackhandImpulse ) + EVENT( AI_EnemyCloseToObstruction, hhCenturion::Event_EnemyCloseToObstruction ) + EVENT( AI_TakeDamage, hhCenturion::Event_TakeDamage ) + EVENT( AI_FindNearbyEnemy, hhCenturion::Event_FindNearbyEnemy ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +void hhCenturion::Spawn() { + AI_CENTURION_ARM_MISSING = false; + AI_CENTURION_REQUIRE_ROAR = false; + AI_CENTURION_ARM_TUNNEL = false; + AI_CENTURION_SCRIPTED_ROAR = 0; + AI_CENTURION_FORCEFIELD_WAIT = false; + AI_CENTURION_SCRIPTED_TUNNEL = 0; +} + +void hhCenturion::Event_PostSpawn( void ) { + hhMonsterAI::Event_PostSpawn(); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" ); + idEntityPtr ent; + while ( kv ) { + ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent.IsValid() ) { + obstacles.AddUnique( ent ); + } + kv = spawnArgs.MatchPrefix( "aasObstacle", kv ); + } +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhCenturion::LinkScriptVariables( void ) { + hhMonsterAI::LinkScriptVariables(); + + LinkScriptVariable(AI_CENTURION_ARM_MISSING); + LinkScriptVariable(AI_CENTURION_REQUIRE_ROAR); + LinkScriptVariable(AI_CENTURION_ARM_TUNNEL); + LinkScriptVariable(AI_CENTURION_SCRIPTED_ROAR); + LinkScriptVariable(AI_CENTURION_SCRIPTED_TUNNEL); + LinkScriptVariable(AI_CENTURION_FORCEFIELD_WAIT); +} + + +void hhCenturion::Event_ForceFieldNotify() { + if( !armchop_Target.GetEntity() ) { + return; //must not be in arm-chop mode, so we don't care yet + } + AI_CENTURION_REQUIRE_ROAR = true; + AI_CENTURION_FORCEFIELD_WAIT = true; +} + +void hhCenturion::Event_ForceFieldToggle( int toggle ) { + if( !armchop_Target.GetEntity() ) { + return; + } + AI_CENTURION_FORCEFIELD_WAIT = false; + if (!toggle) { + AI_CENTURION_REQUIRE_ROAR = false; + } else if( AI_CENTURION_ARM_TUNNEL ) { + Event_ScriptedArmChop(); + return; + } +} + +void hhCenturion::Event_PlayerInTunnel( int toggle, idEntity* ent ) { + if( AI_CENTURION_ARM_MISSING ) { //don't care if the player goes back in tunnel once we already lost our arm + return; + } + + if( toggle ) { + if( !ent ) { + gameLocal.DWarning( "entity needs to be present for tunnel event" ); + return; + } + armchop_Target = ent; + AI_CENTURION_SCRIPTED_TUNNEL = 1; + } + else { + armchop_Target = NULL; + AI_CENTURION_SCRIPTED_TUNNEL = 0; + } +} + +void hhCenturion::Event_MoveToTunnel() { + if (!armchop_Target.IsValid()) { + idThread::ReturnInt(0); + return; + } + + StopMove(MOVE_STATUS_DONE); + MoveToPosition(armchop_Target->GetOrigin()); + idThread::ReturnInt(1); +} + +void hhCenturion::Event_ReachedTunnel() { + if( armchop_Target.IsValid() ) { + idAngles faceAngles = armchop_Target->GetAxis()[0].ToAngles(); + ideal_yaw = faceAngles.yaw; + current_yaw = faceAngles.yaw; + SetAxis( armchop_Target->GetAxis() ); + } +} + +void hhCenturion::Event_ScriptedRoar() { + if ( AI_CENTURION_SCRIPTED_ROAR > 0 ) { + gameLocal.Warning( "centurionRoar() called more than once!\n"); + } else { + AI_CENTURION_SCRIPTED_ROAR = 1; + } +} + +void hhCenturion::Event_ScriptedArmChop() { + const function_t* newstate = NULL; + newstate = GetScriptFunction( "state_ScriptedArmChop" ); + if( newstate ) { + SetState( newstate ); + } + else { + gameLocal.Warning( "Unable to find 'state_ScriptedArmChop' on centurion" ); + } +} + +void hhCenturion::Event_CenturionLooseArm() { + idDict args; + idEntity* ent = NULL; + idVec3 jointLoc; + idMat3 jointAxis; + + animator.GetJointTransform( spawnArgs.GetString("arm_severjoint", ""), gameLocal.time, jointLoc, jointAxis ); + jointLoc = renderEntity.origin + jointLoc * renderEntity.axis; + args.SetVector( "origin", jointLoc ); + args.SetMatrix( "rotation", renderEntity.axis ); + args.SetBool( "spin", 0 ); + args.SetFloat( "triggersize", 48.f ); + args.SetBool( "enablePickup", true ); + args.SetFloat( "respawn", 0.f ); + ent = gameLocal.SpawnObject( spawnArgs.GetString("def_arm_weaponclass", ""), &args ); + + SetSkinByName( spawnArgs.GetString( "skin_arm_gone" ) ); + + hhFxInfo fx; + fx.SetEntity( this ); + fx.RemoveWhenDone( true ); + SpawnFxLocal( spawnArgs.GetString( "fx_armchop" ), jointLoc, mat3_identity, &fx ); + + //drop any stuck arrows + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); + ent->Hide(); + ent->PostEventSec( &EV_Remove, 5 ); + next = teamChain; + } + } + + AI_CENTURION_ARM_MISSING = true; +} + +void hhCenturion::Event_CenturionLaunch( const idList* parmList ) { + const idDict* projDef = NULL; + const idSoundShader* soundShader = NULL; + + // parms: joint, projectileDef, sound, fx + if( !parmList || parmList->Num() != 5 ) { + gameLocal.Warning( "Incorrect paramater number" ); + return; + } +//Rbarrel_A projectile_centurion_autocannon snd_fire fx_muzzleFlash + const char* jointName = (*parmList)[ 0 ].c_str(); + const char* projectileDefName = (*parmList)[ 1 ].c_str(); + const char* soundName = (*parmList)[ 2 ].c_str(); + const char* fxName = (*parmList)[ 3 ].c_str(); + int autoAim = atoi( (*parmList)[ 4 ].c_str() ); + + if( AI_CENTURION_ARM_MISSING ) { //skip firing if joint is on the severed arm + if( !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointA", "")) || !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointB", "")) ) { + return; + } + } + + projDef = gameLocal.FindEntityDefDict( projectileDefName, false ); + HH_ASSERT( !shootTarget.IsValid() ); + // If autoAim is true and we're not blending, and we're facing the enemy, auto-aim + if ( ( autoAim == 1 || ( autoAim == 2 && gameLocal.random.RandomInt(100) < 50 ) ) && torsoAnim.animBlendFrames == 0 && FacingEnemy( 5.0f ) ) { + AimedAttackMissile( jointName, projDef ); + } else { // Otherwise do a normal missile attack + Event_AttackMissile( jointName, projDef, 1 ); + } + + if(idStr::Cmpn( soundName, "snd_", 4)) { + soundShader = declManager->FindSound( soundName ); + if( soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", soundName ); + } + StartSoundShader( soundShader, SND_CHANNEL_WEAPON, 0, false, NULL ); + } + else { + if( !StartSound(soundName, SND_CHANNEL_WEAPON, 0, false, NULL) ) { + gameLocal.Warning( "Framecommand 'centurionFire' on entity '%s' could not find sound '%s'", GetName(), soundName ); + } + } + BroadcastFxInfoAlongBone( spawnArgs.GetString(fxName), jointName ); +} + + +void hhCenturion::Event_DestroyObstruction() { + if( pillarEntity.IsValid() ) { + pillarEntity->PostEventMS( &EV_Activate, 0.f, this ); + } +} + +void hhCenturion::Event_CheckForObstruction( int checkPathToPillar ) { + bool obstacle = false; + predictedPath_t path; + if( enemy.IsValid() ) { + idVec3 end = enemy->GetPhysics()->GetOrigin(); + if ( !checkPathToPillar ) { + trace_t tr; + idVec3 toPos, eye = GetEyePosition(); + + if ( enemy->IsType( idActor::Type ) ) { + toPos = ( ( idActor * )enemy.GetEntity() )->GetEyePosition(); + } else { + toPos = enemy->GetPhysics()->GetOrigin(); + } + + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + + idEntity *traceEnt = gameLocal.GetTraceEntity( tr ); + if ( traceEnt && ( tr.fraction < 1.0f || traceEnt != enemy.GetEntity() ) ) { + //check to see if the other object is an pillar... + if( traceEnt->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) { + pillarEntity = traceEnt; + obstacle = true; + } + } + + } else if ( pillarEntity.IsValid() ) { + idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), enemy->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + if( path.endEvent != 0 && path.blockingEntity ) { + //check to see if the other object is an pillar... + if( path.blockingEntity->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) { + //check to see if we can path to the obstacle clearly + pillarEntity = path.blockingEntity; + //idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), pillarEntity->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA), path ); + //if( path.endEvent != 0 && path.blockingEntity == pillarEntity.GetEntity() ) { + obstacle = true; + //} + } + } + } + } + idThread::ReturnInt( (int)obstacle ); +} + +void hhCenturion::Event_CloseToObstruction() { + if( physicsObj.GetAbsBounds().IntersectsBounds(pillarEntity->GetPhysics()->GetAbsBounds()) ) { + idThread::ReturnInt( 1 ); + return; + } + idThread::ReturnInt( 0 ); +} + +void hhCenturion::Event_ReachedObstruction() { + FaceEntity( pillarEntity.GetEntity() ); +} + +void hhCenturion::Event_MoveToObstruction() { + idVec3 temp; + if ( aas ) { + int toAreaNum = PointReachableAreaNum( pillarEntity.GetEntity()->GetOrigin() ); + temp = pillarEntity.GetEntity()->GetOrigin(); + aas->PushPointIntoAreaNum( toAreaNum, temp ); + MoveToPosition( temp ); + } else { + gameLocal.Warning( "Centurion has no aas for MoveToObstruction\n" ); + } +} + +void hhCenturion::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + + if(ai_debugBrain.GetInteger() > 0 && state) { + if ( enemy.IsValid() && enemy->GetHealth() > 0 ) { + float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast(); + gameRenderWorld->DrawText( va("%f", dist), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + gameRenderWorld->DrawText(state->Name(), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + gameRenderWorld->DrawText(torsoAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + gameRenderWorld->DrawText(legsAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } +} + +bool hhCenturion::AttackMelee( const char *meleeDefName ) { + const idDict *meleeDef; + idActor *enemyEnt = enemy.GetEntity(); + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName ); + } + + if ( !enemyEnt ) { + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // make sure the trace can actually hit the enemy + if ( !TestMelee() ) { + // missed + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // 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; + + enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + if ( enemyEnt->IsActiveAF() && !enemyEnt->IsType( idPlayer::Type ) ) { + PostEventMS( &AI_BackhandImpulse, 0, enemyEnt ); + } + + lastAttackTime = gameLocal.time; + + return true; +} + +void hhCenturion::Event_BackhandImpulse( idEntity* ent ) { + const idDict *meleeDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_impulse_damage"), false ); + if ( !meleeDef || !ent ) { + return; + } + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + idVec3 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + globalKickDir *= ent->spawnArgs.GetFloat( "kick_scale", "1.0" ); + ent->ApplyImpulse( this, 0, ent->GetOrigin(), meleeDef->GetFloat( "push_ragdoll" ) * globalKickDir ); +} + +/* +===================== +hhCenturion::Save +===================== +*/ +void hhCenturion::Save( idSaveGame *savefile ) const { + armchop_Target.Save( savefile ); + pillarEntity.Save( savefile ); +} + +/* +===================== +hhCenturion::Restore +===================== +*/ +void hhCenturion::Restore( idRestoreGame *savefile ) { + armchop_Target.Restore( savefile ); + pillarEntity.Restore( savefile ); + + // Restore the obstacle list + const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" ); + idEntityPtr ent; + while ( kv ) { + ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent.IsValid() ) { + obstacles.AddUnique( ent ); + } + kv = spawnArgs.MatchPrefix( "aasObstacle", kv ); + } +} + +void hhCenturion::Event_EnemyCloseToObstruction(void) { + bool close = false; + if ( pillarEntity.IsValid() && enemy.IsValid() ) { + float obs_dist = ( pillarEntity->GetOrigin() - GetOrigin() ).Length(); + float dist = ( enemy->GetOrigin() - GetOrigin() ).Length(); + + // If the enemy is further from me than the pillar... + if ( dist > obs_dist) { + dist = ( pillarEntity->GetOrigin() - enemy->GetOrigin() ).Length(); + // If the enemy is less than 400 units from the pillar, then he's 'close' + if ( dist < 400.0f ) { + close = true; + } + } + } + idThread::ReturnInt( (int) close ); +} + +void hhCenturion::AimedAttackMissile( const char *jointname, const idDict *projDef) { + idProjectile *proj; + idVec3 target, origin = GetOrigin(); + bool inShuttle = false; + + if ( shootTarget.IsValid() ) { + target = shootTarget->GetOrigin(); + } else if ( enemy.IsValid() ) { + target = enemy->GetOrigin(); + if ( enemy->IsType( idActor::Type ) ) { + target.z += enemy->EyeHeight() / 4.0f; + } + } else { + // No target? Do the default attack + Event_AttackMissile( jointname, projDef, 1 ); + return; + } + + // If target is too close do a non-aimed attack + if ( fabsf( origin.x - target.x ) < 256 && + fabsf( origin.y - target.y ) < 256 ) { + Event_AttackMissile( jointname, projDef, 1 ); + return; + } + + idVec3 dist = origin - target; + + if ( shootTarget.IsValid() ) { + proj = LaunchProjectile( jointname, shootTarget.GetEntity(), true, projDef ); + } else { + proj = LaunchProjectile( jointname, enemy.GetEntity(), true, projDef ); + } +} + +void hhCenturion::Event_TakeDamage(int takeDamage) { + // Set the takedamage flag + fl.takedamage = (takeDamage != 0); +} + +void hhCenturion::Event_FindNearbyEnemy( float distance ) { + // Search for the monster nearest to us + idAI *nearest = NULL; + float dist, nearDist = idMath::INFINITY; + idAI *ai = reinterpret_cast (gameLocal.FindEntityOfType( idAI::Type, NULL )); + while (ai) { + if (ai != this) { // Don't target yourself + dist = (ai->GetOrigin() - GetOrigin()).Length(); + if (dist < nearDist) { + nearDist = dist; + nearest = ai; + } + } + ai = reinterpret_cast (gameLocal.FindEntityOfType( idAI::Type, ai )); + } + + // If we found one near us and it's near enough, return it + if (nearest && nearDist < distance) { + idThread::ReturnEntity(nearest); + } else { + idThread::ReturnEntity(NULL); + } +} + +void hhCenturion::Event_Touch( idEntity *other, trace_t *trace ) { + if ( (!enemy.GetEntity() || other->IsType( hhPlayer::Type )) && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { + Activate( other ); + SetEnemy( static_cast ( other ) ); + } + AI_PUSHED = true; +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_centurion.h b/neo/prey/Prey/ai_centurion.h new file mode 100644 index 00000000..3175c9f8 --- /dev/null +++ b/neo/prey/Prey/ai_centurion.h @@ -0,0 +1,84 @@ +#ifndef __PREY_AI_CENTURION_H__ +#define __PREY_AI_CENTURION_H__ + +/*********************************************************************** + hhCenturion. + Centurion AI. +***********************************************************************/ +class hhCenturion : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhCenturion); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_CenturionLaunch( const idList* parmList ) {}; + void Event_ScriptedRoar() {}; + void Event_ScriptedArmChop() {}; + void Event_CenturionLooseArm() {}; + void Event_ForceFieldNotify() {}; + void Event_ForceFieldToggle( int toggle ) {}; + void Event_PlayerInTunnel( int toggle, idEntity* ent ) {}; + void Event_ReachedTunnel() {}; + void Event_MoveToTunnel() {}; + + bool AttackMelee( const char *meleeDefName ) { return true; }; + void Event_CheckForObstruction( int checkPathToPillar ) {}; + void Event_MoveToObstruction() {}; + void Event_DestroyObstruction() {}; + void Event_ReachedObstruction() {}; + void Event_CloseToObstruction() {}; + void Event_EnemyCloseToObstruction() {}; + void Event_BackhandImpulse( idEntity* ent ) {}; + virtual void Event_PostSpawn( void ) {}; + void Event_TakeDamage( int takeDamage ) {}; + void Event_FindNearbyEnemy( float distance ) {}; + virtual void Event_Touch( idEntity *other, trace_t *trace ) {}; +#else + +public: + void Spawn(); + void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +protected: + void Event_CenturionLaunch( const idList* parmList ); + void Event_ScriptedRoar(); + void Event_ScriptedArmChop(); + void Event_CenturionLooseArm(); + void Event_ForceFieldNotify(); + void Event_ForceFieldToggle( int toggle ); + void Event_PlayerInTunnel( int toggle, idEntity* ent ); + void Event_ReachedTunnel(); + void Event_MoveToTunnel(); + + void LinkScriptVariables( void ); + bool AttackMelee( const char *meleeDefName ); + void Event_CheckForObstruction( int checkPathToPillar ); + void Event_MoveToObstruction(); + void Event_DestroyObstruction(); + void Event_ReachedObstruction(); + void Event_CloseToObstruction(); + void Event_EnemyCloseToObstruction(); + void Event_BackhandImpulse( idEntity* ent ); + virtual void Event_PostSpawn( void ); + void Event_TakeDamage( int takeDamage ); + void Event_FindNearbyEnemy( float distance ); + virtual void Event_Touch( idEntity *other, trace_t *trace ); + + void AimedAttackMissile( const char *jointname, const idDict *projDef ); + + idEntityPtr armchop_Target; + idEntityPtr pillarEntity; + + idScriptBool AI_CENTURION_ARM_MISSING; + idScriptBool AI_CENTURION_REQUIRE_ROAR; + idScriptBool AI_CENTURION_ARM_TUNNEL; + idScriptBool AI_CENTURION_FORCEFIELD_WAIT; + idScriptFloat AI_CENTURION_SCRIPTED_ROAR; + idScriptFloat AI_CENTURION_SCRIPTED_TUNNEL; + + idList< idEntityPtr > obstacles; +#endif +}; + +#endif //__PREY_AI_CENTURION_H__ \ No newline at end of file diff --git a/neo/prey/Prey/ai_crawler.cpp b/neo/prey/Prey/ai_crawler.cpp new file mode 100644 index 00000000..c6df719f --- /dev/null +++ b/neo/prey/Prey/ai_crawler.cpp @@ -0,0 +1,115 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhMonsterAI, hhCrawler ) + EVENT( EV_Touch, hhCrawler::Event_Touch ) +END_CLASS + + +void hhCrawler::Spawn() { + if( spawnArgs.FindKey("def_pickup") ) { + if ( gameLocal.isMultiplayer ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_MONSTERCLIP ); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + } +} + +void hhCrawler::Event_Touch( idEntity *other, trace_t *trace ) { + hhPlayer *player; + + if( !spawnArgs.FindKey("def_pickup") || !other->IsType(idPlayer::Type) ) { + return; + } + + player = static_cast(other); + + Pickup( player ); +} + +void hhCrawler::Pickup( hhPlayer *player ) { + + if( !GiveToPlayer( player ) ) { + return; + } + + // play pickup sound + StartSoundShader( refSound.shader, SND_CHANNEL_ITEM, false ); // play what's defined in the entity + + // trigger our targets + ActivateTargets( player ); + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model + Hide(); + + if (player->hud) { + player->hud->SetStateInt("item", 1); + player->hud->SetStateString("itemtext", spawnArgs.GetString("inv_name")); + player->hud->SetStateString("itemicon", spawnArgs.GetString("inv_icon")); + } + + idStr str; + spawnArgs.GetString("inv_name", "Item", str); + + if (player == gameLocal.GetClientByNum(gameLocal.localClientNum)) { + gameLocal.Printf("Picked up a %s\n", str.c_str()); + } + + + PostEventMS( &EV_Remove, 2000 ); +} + +bool hhCrawler::GiveToPlayer( hhPlayer* player ) { + const char *pickupName = spawnArgs.GetString("def_pickup", NULL); + bool pickedUp = false; + + if ( player && pickupName ) { + idEntity *ent = gameLocal.SpawnObject(pickupName, NULL); + if (ent->IsType(hhItem::Type)) { + pickedUp = player->GiveItem( static_cast(ent) ); + } + ent->Hide(); + ent->PostEventMS(&EV_Remove, 2000); + } + + return pickedUp; +} + +void hhCrawler::Think( void ) { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { //HUMANHEAD rww + return; + } + + if ( thinkFlags & TH_THINK ) { + current_yaw += deltaViewAngles.yaw; + ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); + + // HUMANHEAD NLA + physicsObj.ResetNumTouchEnt(0); + // HUMANHEAD END + // animation based movement + UpdateAIScript(); + AnimMove(); + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + UpdateAnimation(); + Present(); + LinkCombat(); +} + +bool hhCrawler::UpdateAnimationControllers() { + //do nothing + return false; +} \ No newline at end of file diff --git a/neo/prey/Prey/ai_crawler.h b/neo/prey/Prey/ai_crawler.h new file mode 100644 index 00000000..d3dbaec8 --- /dev/null +++ b/neo/prey/Prey/ai_crawler.h @@ -0,0 +1,24 @@ +#ifndef __PREY_AI_CRAWLER_H__ +#define __PREY_AI_CRAWLER_H__ + +/*********************************************************************** + hhCrawler. + Crawler AI. +***********************************************************************/ +class hhCrawler : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhCrawler); + +public: + void Spawn(); + +protected: + void Event_Touch( idEntity *other, trace_t *trace ); + void Pickup( hhPlayer *player ); + bool GiveToPlayer( hhPlayer* player ); + void Think(); + bool UpdateAnimationControllers(); +}; + +#endif //__PREY_AI_CRAWLER_H__ \ No newline at end of file diff --git a/neo/prey/Prey/ai_creaturex.cpp b/neo/prey/Prey/ai_creaturex.cpp new file mode 100644 index 00000000..6863ea90 --- /dev/null +++ b/neo/prey/Prey/ai_creaturex.cpp @@ -0,0 +1,1669 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef CX_LaserOn("sunBeamOn", NULL, 'd'); +const idEventDef CX_LaserOff("sunBeamOff"); +const idEventDef MA_UpdateLasers(""); +const idEventDef MA_AssignLeftMuzzleFx( "", "e" ); +const idEventDef MA_AssignRightMuzzleFx( "", "e" ); +const idEventDef MA_AssignLeftImpactFx( "", "e" ); +const idEventDef MA_AssignRightImpactFx( "", "e" ); +const idEventDef MA_AssignLeftRechargeFx( "", "e" ); +const idEventDef MA_AssignRightRechargeFx( "", "e" ); +const idEventDef MA_SetGunOffset( "setGunOffset", "v" ); +const idEventDef MA_EndLeftBeams( "" ); +const idEventDef MA_EndRightBeams( "" ); +const idEventDef MA_HudEvent( "hudEvent", "s" ); +const idEventDef MA_GunRecharge( "gunRecharge", "d" ); +const idEventDef MA_EndRecharge( "endRecharge" ); +const idEventDef MA_ResetRechargeBeam( "" ); +const idEventDef MA_SparkLeft( "" ); +const idEventDef MA_SparkRight( "" ); +const idEventDef MA_LeftGunDeath( "" ); +const idEventDef MA_RightGunDeath( "" ); +const idEventDef MA_StartRechargeBeams( "" ); + +CLASS_DECLARATION(hhMonsterAI, hhCreatureX) + EVENT(CX_LaserOn, hhCreatureX::Event_LaserOn ) + EVENT(CX_LaserOff, hhCreatureX::Event_LaserOff ) + EVENT(MA_UpdateLasers, hhCreatureX::Event_UpdateLasers ) + EVENT(MA_AssignRightMuzzleFx, hhCreatureX::Event_AssignRightMuzzleFx ) + EVENT(MA_AssignLeftMuzzleFx, hhCreatureX::Event_AssignLeftMuzzleFx ) + EVENT(MA_AssignRightImpactFx, hhCreatureX::Event_AssignRightImpactFx ) + EVENT(MA_AssignLeftImpactFx, hhCreatureX::Event_AssignLeftImpactFx ) + EVENT(MA_SetGunOffset, hhCreatureX::Event_SetGunOffset ) + EVENT(MA_EndLeftBeams, hhCreatureX::Event_EndLeftBeams ) + EVENT(MA_EndRightBeams, hhCreatureX::Event_EndRightBeams ) + EVENT(MA_HudEvent, hhCreatureX::Event_HudEvent ) + EVENT(MA_GunRecharge, hhCreatureX::Event_GunRecharge ) + EVENT(MA_EndRecharge, hhCreatureX::Event_EndRecharge ) + EVENT(MA_ResetRechargeBeam, hhCreatureX::Event_ResetRechargeBeam ) + EVENT(MA_SparkLeft, hhCreatureX::Event_SparkLeft ) + EVENT(MA_SparkRight, hhCreatureX::Event_SparkRight ) + EVENT(MA_LeftGunDeath, hhCreatureX::Event_LeftGunDeath ) + EVENT(MA_RightGunDeath, hhCreatureX::Event_RightGunDeath ) + EVENT(MA_StartRechargeBeams, hhCreatureX::Event_StartRechargeBeams ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +void hhCreatureX::Spawn() { + idVec3 boneOrigin; + idMat3 boneAxis; + targetStart_L = vec3_zero; + targetStart_R = vec3_zero; + targetEnd_L = vec3_zero; + targetEnd_R = vec3_zero; + targetCurrent_L = vec3_zero; + targetCurrent_R = vec3_zero; + bLaserLeftActive = false; + bLaserRightActive = false; + nextBeamTime = 0; + nextLeftZapTime = 0; + nextRightZapTime = 0; + nextLaserLeft = 0; + nextLaserRight = 0; + nextHealthTick = 0; + rightGunLives = spawnArgs.GetInt( "gun_lives", "3" ); + leftGunLives = spawnArgs.GetInt( "gun_lives", "3" ); + rightGunHealth = spawnArgs.GetInt( "gun_health", "20" ); + leftGunHealth = spawnArgs.GetInt( "gun_health", "20" ); + bScripted = spawnArgs.GetBool( "scripted", "0" ); + numBurstBeams = 0; + + if ( bScripted ) { + //don't spawn any combat related entities if scripted + return; + } + + laserRight = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( laserRight.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin + viewAxis[0] * 60; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + laserRight->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserRight->Activate( false ); + } + laserLeft = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( laserLeft.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin + viewAxis[0] * 60; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + laserLeft->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserLeft->Activate( false ); + } + preLaserRight = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( preLaserRight.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + preLaserRight->SetOrigin( boneOrigin ); + preLaserRight->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + preLaserRight->Activate( false ); + } + preLaserLeft = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + if( preLaserLeft.IsValid() ) { + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + + idVec3 junkOrigin = boneOrigin; //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin + + preLaserLeft->SetOrigin( boneOrigin ); + preLaserLeft->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + preLaserLeft->Activate( false ); + } + PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) ); + + leftBeamList.Clear(); + rightBeamList.Clear(); + leftRechargeBeam.Clear(); + rightRechargeBeam.Clear(); + leftRecharger.Clear(); + rightRecharger.Clear(); + if ( spawnArgs.GetBool( "use_recharge" ) ) { + numBurstBeams = spawnArgs.GetInt( "num_burst_beams", "4" ); + for ( int i = 0; i < numBurstBeams; i ++ ) { + leftBeamList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst" ) ) ); + if( leftBeamList[i].IsValid() ) { + leftBeamList[i]->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("damage_bone_left"), boneOrigin, boneAxis ); + leftBeamList[i]->SetOrigin( boneOrigin ); + leftBeamList[i]->BindToJoint( this, spawnArgs.GetString("damage_bone_left"), false ); + } + rightBeamList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst" ) ) ); + if( rightBeamList[i].IsValid() ) { + rightBeamList[i]->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("damage_bone_right"), boneOrigin, boneAxis ); + rightBeamList[i]->SetOrigin( boneOrigin ); + rightBeamList[i]->BindToJoint( this, spawnArgs.GetString("damage_bone_right"), false ); + } + } + + leftRechargeBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRecharge" ) ); + if( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + leftRechargeBeam->SetOrigin( boneOrigin ); + leftRechargeBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + leftRechargeBeam->Hide(); + } + rightRechargeBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRecharge" ) ); + if( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + rightRechargeBeam->SetOrigin( boneOrigin ); + rightRechargeBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + rightRechargeBeam->Hide(); + } + leftDamageBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamDamage" ) ); + if( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + leftDamageBeam->SetOrigin( boneOrigin ); + leftDamageBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + leftDamageBeam->Hide(); + } + rightDamageBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamDamage" ) ); + if( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + rightDamageBeam->SetOrigin( boneOrigin ); + rightDamageBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + rightDamageBeam->Hide(); + } + leftRetractBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRetract" ) ); + if( leftRetractBeam.IsValid() ) { + leftRetractBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + leftRetractBeam->SetOrigin( boneOrigin ); + leftRetractBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false ); + leftRetractBeam->Hide(); + } + rightRetractBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRetract" ) ); + if( rightRetractBeam.IsValid() ) { + rightRetractBeam->Activate( false ); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + rightRetractBeam->SetOrigin( boneOrigin ); + rightRetractBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false ); + rightRetractBeam->Hide(); + } + + idEntity *ent; + idDict dict; + const idDict *rechargeDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_recharger")); + if ( !rechargeDict ) { + gameLocal.Error( "Unknown def_recharger: %s\n", spawnArgs.GetString("def_recharger") ); + } + dict.Copy(*rechargeDict); + gameLocal.SpawnEntityDef( dict, &ent ); + if ( ent ) { + leftRecharger.Assign( ent ); + leftRecharger->GetPhysics()->DisableClip(); + leftRecharger->GetPhysics()->SetClipMask( 0 ); + leftRecharger->HideNoDormant(); + leftRecharger->SetOrigin( GetOrigin() ); + leftRecharger->SetEnemy( this ); + leftRecharger->spawnArgs.Set( "left_recharger", "1" ); //set key for left or right + } + ent = NULL; + gameLocal.SpawnEntityDef( dict, &ent ); + if ( ent ) { + rightRecharger.Assign( ent ); + rightRecharger->GetPhysics()->DisableClip(); + rightRecharger->GetPhysics()->SetClipMask( 0 ); + rightRecharger->HideNoDormant(); + rightRecharger->SetOrigin( GetOrigin() ); + rightRecharger->SetEnemy( this ); + rightRecharger->spawnArgs.Set( "right_recharger", "1" ); //set key for left or right + } + } +} + +hhCreatureX::~hhCreatureX() { + MuzzleLeftOff(); + MuzzleRightOff(); + + SAFE_REMOVE( laserRight ); + SAFE_REMOVE( laserLeft ); + SAFE_REMOVE( preLaserLeft ); + SAFE_REMOVE( preLaserRight ); +} + +void hhCreatureX::Event_LaserOn() { + if ( !enemy.IsValid() ) { + idThread::ReturnInt( false ); + return; + } + if ( !AI_RIGHT_DAMAGED ) { + bLaserRightActive = true; + } + if ( !AI_LEFT_DAMAGED ) { + bLaserLeftActive = true; + } + idVec3 toEnemy = GetEnemy()->GetOrigin() - GetOrigin(); + targetStart_L = GetOrigin() + spawnArgs.GetFloat( "test_1", "0.5" ) * toEnemy; + targetStart_L.z = GetOrigin().z; + float distToStart = (targetStart_L - GetOrigin()).LengthFast(); + if ( distToStart < 200 ) { + targetStart_L = GetOrigin() + toEnemy; + targetStart_L.z = GetOrigin().z; + } + targetStart_R = GetOrigin() + spawnArgs.GetFloat( "test_1", "0.5" ) * toEnemy; + targetStart_R.z = GetOrigin().z; + distToStart = (targetStart_R - GetOrigin()).LengthFast(); + if ( distToStart < 200 ) { + targetStart_R = GetOrigin() + toEnemy; + targetStart_R.z = GetOrigin().z; + } + targetEnd_L = GetOrigin() + spawnArgs.GetFloat( "test_2", "1.4" ) * toEnemy; + targetEnd_L.z = GetOrigin().z + spawnArgs.GetFloat( "test_4", "50" ); + targetEnd_R = GetOrigin() + spawnArgs.GetFloat( "test_2", "1.4" ) * toEnemy; + targetEnd_R.z = GetOrigin().z + spawnArgs.GetFloat( "test_4", "50" ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, targetStart_L, targetEnd_L, 10, 10000 ); + } + targetAlpha_L = 0.0f; + targetAlpha_R = 0.0f; + if ( !bLaserLeftActive && !bLaserRightActive ) { + idThread::ReturnInt( false ); + } + idThread::ReturnInt( true ); +} + +void hhCreatureX::Event_LaserOff() { + bLaserLeftActive = false; + bLaserRightActive = false; + targetStart_L = vec3_zero; + targetStart_R = vec3_zero; + targetEnd_L = vec3_zero; + targetEnd_R = vec3_zero; + MuzzleLeftOff(); + MuzzleRightOff(); +} + +void hhCreatureX::Event_UpdateLasers() { + if ( !enemy.IsValid() ) { + PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) ); + return; + } + trace_t trace; + float dist = 0; + idVec3 boneOrigin, traceEnd; + idMat3 boneAxis; + idEntity *hitEntity = NULL; + int hitCount = 0; + if ( laserRight.IsValid() && bLaserRightActive ) { + MuzzleRightOn(); + traceEnd = targetCurrent_R + 2000 * (targetCurrent_R - laserRight->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserRight->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, laserRight->GetOrigin(), traceEnd, 10, 200 ); + } + if ( trace.fraction < 1.0f ) { + dist = (trace.endpos - laserRight->GetOrigin()).Length(); + hitEntity = gameLocal.GetTraceEntity( trace ); + if ( hitEntity ) { + hitEntity->Damage( this, this, hitEntity->GetOrigin() - GetOrigin().ToNormal(), spawnArgs.GetString( "def_laserDamage" ), 1.0, 0 ); + } + } + } else { + MuzzleRightOff(); + } + if ( laserLeft.IsValid() && bLaserLeftActive ) { + MuzzleLeftOn(); + traceEnd = targetCurrent_L + 2000 * (targetCurrent_L - laserLeft->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserLeft->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, laserLeft->GetOrigin(), traceEnd, 10, 200 ); + } + if ( trace.fraction < 1.0f ) { + dist = (trace.endpos - laserLeft->GetOrigin()).Length(); + hitEntity = gameLocal.GetTraceEntity( trace ); + if ( hitEntity ) { + hitEntity->Damage( this, this, hitEntity->GetOrigin() - GetOrigin().ToNormal(), spawnArgs.GetString( "def_laserDamage" ), 1.0, 0 ); + } + } + } else { + MuzzleLeftOff(); + } + PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) ); +} + +void hhCreatureX::Event_ResetRechargeBeam() { + if ( !AI_RECHARGING ) { + return; + } + if ( leftRecharger.IsValid() ) { + leftRecharger->SetShaderParm( 6, 0.0f ); + } + if ( rightRecharger.IsValid() ) { + rightRecharger->SetShaderParm( 6, 0.0f ); + } + if ( rechargeLeftFx.IsValid() ) { + rechargeLeftFx->SetParticleShaderParm( 6, 0.0f ); + } + if ( rechargeRightFx.IsValid() ) { + rechargeRightFx->SetParticleShaderParm( 6, 0.0f ); + } + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( true ); + leftRechargeBeam->Show(); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( true ); + rightRechargeBeam->Show(); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + } + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Activate( false ); + } + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Activate( false ); + } +} + +void hhCreatureX::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + idVec3 pastEnemy; + hhMonsterAI::Think(); + if ( AI_RECHARGING ) { + bool damaged = false; + if ( leftRecharger.IsValid() ) { + //recharger damage. switch out beams + if ( leftRechargeBeam.IsValid() && leftRecharger->GetHealth() < leftRechargerHealth ) { + if ( leftRechargerHealth > 0 && !leftRecharger->IsDead() ) { + damaged = true; + } + leftRechargeBeam->Activate( false ); + leftDamageBeam->Show(); + leftDamageBeam->Activate( true ); + leftRecharger->SetShaderParm( 6, 1.0f ); + if ( rechargeLeftFx.IsValid() ) { + rechargeLeftFx->SetParticleShaderParm( 6, 1.0f ); + } + } + leftRechargerHealth = leftRecharger->GetHealth(); + } + if ( rightRecharger.IsValid() ) { + //recharger damage. switch out beams + if ( rightRechargeBeam.IsValid() && rightRecharger->GetHealth() < rightRechargerHealth ) { + if ( rightRechargerHealth > 0 && !rightRecharger->IsDead() ) { + damaged = true; + } + rightRechargeBeam->Activate( false ); + rightDamageBeam->Show(); + rightDamageBeam->Activate( true ); + rightRecharger->SetShaderParm( 6, 1.0f ); + if ( rechargeRightFx.IsValid() ) { + rechargeRightFx->SetParticleShaderParm( 6, 1.0f ); + } + } + rightRechargerHealth = rightRecharger->GetHealth(); + } + if ( damaged ) { + PostEventSec( &MA_ResetRechargeBeam, spawnArgs.GetFloat( "damage_beam_duration", "0.8" ) ); + } + if ( !AI_LEFT_DAMAGED && leftRecharger.IsValid() && leftRecharger->GetHealth() <= 0 ) { + //recharger has died so start retracting the beams and schedule the gun's death + StartSound( "snd_gun_predeath", SND_CHANNEL_ANY ); + SAFE_REMOVE( leftRechargeBeam ); + PostEventSec( &MA_LeftGunDeath, spawnArgs.GetFloat("retract_delay", "0.9") ); + const char *defName = spawnArgs.GetString( "fx_retract" ); + if ( defName && defName[0] && leftDamageBeam.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + retractLeftFx = SpawnFxLocal( defName, leftDamageBeam->GetTargetLocation(), mat3_identity, &fxInfo, gameLocal.isClient ); + } + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Show(); + leftRetractBeam->Activate( true ); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + leftDamageBeam->Hide(); + } + AI_LEFT_DAMAGED = true; + } + if ( !AI_RIGHT_DAMAGED && rightRecharger.IsValid() && rightRecharger->GetHealth() <= 0 ) { + //recharger has died so start retracting the beams and schedule the gun's death + StartSound( "snd_gun_predeath", SND_CHANNEL_ANY ); + SAFE_REMOVE( rightRechargeBeam ); + PostEventSec( &MA_RightGunDeath, spawnArgs.GetFloat("retract_delay", "1") ); + const char *defName = spawnArgs.GetString( "fx_retract" ); + if ( defName && defName[0] && leftDamageBeam.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + retractRightFx = SpawnFxLocal( defName, rightDamageBeam->GetTargetLocation(), mat3_identity, &fxInfo, gameLocal.isClient ); + } + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Show(); + rightRetractBeam->Activate( true ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + rightDamageBeam->Hide(); + } + AI_RIGHT_DAMAGED = true; + } + if ( AI_HEALTH_TICK && gameLocal.time >= nextHealthTick ) { + health += spawnArgs.GetInt( "recharge_delta", "1" ); + nextHealthTick += spawnArgs.GetInt( "recharge_period" ); + if ( health > spawnArgs.GetInt( "health" ) ) { + AI_HEALTH_TICK = false; + health = spawnArgs.GetInt( "health" ); + } + } + } else { + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( false ); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( false ); + } + } + + if ( AI_LEFT_DAMAGED && gameLocal.time > nextLeftZapTime ) { + nextLeftZapTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "zap_period", "0.3" )); + for ( int i = 0; i < numBurstBeams; i ++ ) { + if ( leftBeamList[i].IsValid() && !leftBeamList[i]->IsActivated() ) { + leftBeamList[i]->Activate( true ); + leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "zap_length", "50" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + PostEventSec(&MA_EndLeftBeams, spawnArgs.GetFloat( "beam_duration_1", "0.2" ) ); + break; + } + } + } + if ( AI_RIGHT_DAMAGED && gameLocal.time > nextRightZapTime ) { + nextRightZapTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "zap_period", "0.3" )); + for ( int i = 0; i < numBurstBeams; i ++ ) { + if ( rightBeamList[i].IsValid() && !rightBeamList[i]->IsActivated() ) { + rightBeamList[i]->Activate( true ); + rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "zap_length", "50" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + PostEventSec(&MA_EndRightBeams, spawnArgs.GetFloat( "beam_duration_1", "0.2" ) ); + break; + } + } + } + + if ( enemy.IsValid() ) { + //left laser + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetAlpha_L += spawnArgs.GetFloat( "laser_move_delta", "0.1" ) * (60.0f * USERCMD_ONE_OVER_HZ); + if ( targetAlpha_L > 1.0f ) { + targetAlpha_L = 1.0f; + } + targetCurrent_L = targetStart_L + targetAlpha_L * ( targetEnd_L - targetStart_L ); + pastEnemy = GetEnemy()->GetOrigin() + idVec3( 0,0,30 ); + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetEnd_L += spawnArgs.GetFloat( "laser_track_delta", "0.1" ) * ( pastEnemy - targetEnd_L ) * (60.0f * USERCMD_ONE_OVER_HZ); + + //right laser + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetAlpha_R += spawnArgs.GetFloat( "laser_move_delta", "0.1" ) * (60.0f * USERCMD_ONE_OVER_HZ); + if ( targetAlpha_R > 1.0f ) { + targetAlpha_R = 1.0f; + } + targetCurrent_R = targetStart_R + targetAlpha_R * ( targetEnd_R - targetStart_R ); + pastEnemy = GetEnemy()->GetOrigin() + idVec3( 0,0,30 ); + //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement + targetEnd_R += spawnArgs.GetFloat( "laser_track_delta", "0.1" ) * ( pastEnemy - targetEnd_R ) * (60.0f * USERCMD_ONE_OVER_HZ); + } + + if ( gameLocal.time > nextBeamTime ) { + nextBeamTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "beam_period", "0.3" )); + for ( int i = 0; i < leftBeamList.Num(); i ++ ) { + if ( leftBeamList[i].IsValid() && leftBeamList[i]->IsActivated() ) { + leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + } + } + for ( int i = 0; i < rightBeamList.Num(); i ++ ) { + if ( rightBeamList[i].IsValid() && rightBeamList[i]->IsActivated() ) { + rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) ); + } + } + } + + idVec3 boneOrigin, traceEnd; + idMat3 boneAxis; + trace_t trace; + memset(&trace, 0, sizeof(trace)); + if ( !enemy.IsValid() ) { + return; + } + + if ( laserRight.IsValid() && bLaserRightActive ) { + traceEnd = targetCurrent_R + 2000 * (targetCurrent_R - laserRight->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserRight->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( trace.fraction < 1.0f ) { + if ( preLaserRight.IsValid() ) { + if ( preLaserRight->IsHidden() ) { + preLaserRight->Show(); + } + preLaserRight->Activate( true ); + preLaserRight->SetTargetEntity( laserRight.GetEntity() ); + } + if ( laserRight->IsHidden() ) { + laserRight->Show(); + } + laserRight->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserRight->Activate( true ); + laserRight->SetTargetLocation( trace.endpos - viewAxis[1] * 10 ); + } + float dist = (laserRight->GetTargetLocation() - laserRight->GetOrigin()).Length(); + if ( dist > 100.0f && impactRightFx.IsValid() ) { + impactRightFx->SetOrigin( laserRight->GetTargetLocation() ); + } + laserEndRight = trace.endpos; + } else { + if ( preLaserRight.IsValid() ) { + preLaserRight->Activate( false ); + } + if ( laserRight.IsValid() ) { + laserRight->Activate( false ); + } + } + + if ( laserLeft.IsValid() && bLaserLeftActive ) { + traceEnd = targetCurrent_L + 2000 * (targetCurrent_L - laserLeft->GetOrigin()).ToNormal(); + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + gameLocal.clip.TracePoint( trace, laserLeft->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this ); + if ( trace.fraction < 1.0f ) { + if ( preLaserLeft.IsValid() ) { + if ( preLaserLeft->IsHidden() ) { + preLaserLeft->Show(); + } + preLaserLeft->Activate( true ); + preLaserLeft->SetTargetEntity( laserLeft.GetEntity() ); + } + if ( laserLeft->IsHidden() ) { + laserLeft->Show(); + } + laserLeft->SetOrigin( boneOrigin + viewAxis[0] * 60 ); + laserLeft->Activate( true ); + laserLeft->SetTargetLocation( trace.endpos + viewAxis[1] * 10 ); + } + float dist = (laserLeft->GetTargetLocation() - laserLeft->GetOrigin()).Length(); + if ( dist > 100.0f && impactLeftFx.IsValid() ) { + impactLeftFx->SetOrigin( laserLeft->GetTargetLocation() ); + } + laserEndLeft = trace.endpos; + } else { + if ( preLaserLeft.IsValid() ) { + preLaserLeft->Activate( false ); + } + if ( laserLeft.IsValid() ) { + laserLeft->Activate( false ); + } + } +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhCreatureX::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_GUN_TRACKING ); + LinkScriptVariable( AI_RECHARGING ); + LinkScriptVariable( AI_LEFT_FIRE ); + LinkScriptVariable( AI_RIGHT_FIRE ); + LinkScriptVariable( AI_LEFT_DAMAGED ); + LinkScriptVariable( AI_RIGHT_DAMAGED ); + LinkScriptVariable( AI_FIRING_LASER ); + LinkScriptVariable( AI_HEALTH_TICK ); + LinkScriptVariable( AI_GUN_EXPLODE ); +} + +void hhCreatureX::Event_RightGunDeath() { + bool explodeGun = false; + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Show(); + rightRetractBeam->Activate( true ); + + //retract the damage beam a little bit + idVec3 bonePos; + idMat3 boneAxis; + GetJointWorldTransform( spawnArgs.GetString( "gun_bone_right" ), bonePos, boneAxis ); + idVec3 lastTargetLocation = rightRetractBeam->GetTargetLocation(); + rightRetractBeam->SetTargetEntity( NULL ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, lastTargetLocation, bonePos, 10, 1 ); + } + rightRetractBeam->SetTargetLocation( lastTargetLocation + spawnArgs.GetFloat("retract_speed", "50") * (bonePos - lastTargetLocation).ToNormal() ); + if ( retractRightFx.IsValid() ) { + retractRightFx->SetOrigin( rightRetractBeam->GetTargetLocation() ); + } + if ( (lastTargetLocation - bonePos).LengthFast() < 30.0f ) { + explodeGun = true; + rightRetractBeam->Activate( false ); + } else { + PostEventSec( &MA_RightGunDeath, 0.01f ); + } + } + + if ( explodeGun || !rightRetractBeam.IsValid() ) { + CancelEvents( &MA_ResetRechargeBeam ); + SAFE_REMOVE( rightRechargeBeam ); + SAFE_REMOVE( retractRightFx ); + for ( int i=0;iActivate( true ); + rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( GetEnemy()->GetOrigin().ToMat3(), 16.0f ) ); + } + } + StartSound( "snd_jenny_scream", SND_CHANNEL_ANY ); + PostEventSec(&MA_EndRightBeams, spawnArgs.GetFloat( "beam_duration_3", "1.0" ) ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_smoke"), spawnArgs.GetString("damage_bone_right"), NULL ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_gun_death"), spawnArgs.GetString("damage_bone_right"), NULL ); + AI_RIGHT_DAMAGED = true; + PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); + if ( AI_LEFT_DAMAGED ) { + SetSkinByName( spawnArgs.GetString( "skin_nogun_both" ) ); + } else { + SetSkinByName( spawnArgs.GetString( "skin_nogun_right" ) ); + } + } +} + +void hhCreatureX::Event_LeftGunDeath() { + bool explodeGun = false; + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Show(); + leftRetractBeam->Activate( true ); + + //retract the damage beam a little bit + idVec3 bonePos; + idMat3 boneAxis; + GetJointWorldTransform( spawnArgs.GetString( "gun_bone_left" ), bonePos, boneAxis ); + idVec3 lastTargetLocation = leftRetractBeam->GetTargetLocation(); + leftRetractBeam->SetTargetEntity( NULL ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, lastTargetLocation, bonePos, 10, 1 ); + } + leftRetractBeam->SetTargetLocation( lastTargetLocation + spawnArgs.GetFloat("retract_speed", "50") * (bonePos - lastTargetLocation).ToNormal() ); + if ( retractLeftFx.IsValid() ) { + retractLeftFx->SetOrigin( leftRetractBeam->GetTargetLocation() ); + } + if ( (lastTargetLocation - bonePos).LengthFast() < 30.0f ) { + explodeGun = true; + leftRetractBeam->Activate( false ); + } else { + PostEventSec( &MA_LeftGunDeath, 0.01f ); + } + } + + if ( explodeGun || !leftRetractBeam.IsValid() ) { + CancelEvents( &MA_ResetRechargeBeam ); + SAFE_REMOVE( leftRechargeBeam ); + SAFE_REMOVE( retractLeftFx ); + for ( int i=0;iActivate( true ); + leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( GetEnemy()->GetOrigin().ToMat3(), 16.0f ) ); + } + } + StartSound( "snd_jenny_scream", SND_CHANNEL_ANY ); + PostEventSec(&MA_EndLeftBeams, spawnArgs.GetFloat( "beam_duration_3", "1.0" ) ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_smoke"), spawnArgs.GetString("damage_bone_left"), NULL ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_gun_death"), spawnArgs.GetString("damage_bone_left"), NULL ); + AI_LEFT_DAMAGED = true; + PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); + if ( AI_RIGHT_DAMAGED ) { + SetSkinByName( spawnArgs.GetString( "skin_nogun_both" ) ); + } else { + SetSkinByName( spawnArgs.GetString( "skin_nogun_left" ) ); + } + } +} + + +bool hhCreatureX::UpdateAnimationControllers( void ) { + idVec3 local; + idVec3 focusPos; + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idAngles diff; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idAFAttachment *headEnt = head.GetEntity(); + idVec3 eyepos; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + + if ( AI_DEAD ) { + + for (int i = 0; i < jawFlapList.Num(); i++) { + jawFlapInfo_t &flapInfo = jawFlapList[i]; + animator.ClearJoint( flapInfo.bone ); + } + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 ); + } + + if ( focusJoint != INVALID_JOINT ) { + if ( headEnt ) { + headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } else { + // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that? + GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } + eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; + } else { + eyepos = GetEyePosition(); + } + + if ( headEnt ) { + CopyJointsFromBodyToHead(); + } + + // 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(); + + idEntity *focusEnt = focusEntity.GetEntity(); + //HUMANHEAD jsh allow eyefocus independent from allowJointMod + if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) || GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1) ) { + focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; + } else if ( focusEnt == NULL ) { + // keep looking at last position until focusTime is up + focusPos = currentFocusPos; + } else if ( focusEnt == enemy.GetEntity() ) { + focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); + } else if ( focusEnt->IsType( idActor::Type ) ) { + focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + focusPos = focusEnt->GetPhysics()->GetOrigin(); + } + + currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; + + // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles + dir = focusPos - orientationJointPos; + newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); + newLookAng.roll = 0.0f; + newLookAng.pitch = 0.0f; + + newLookAng += lookOffset; + +#if 0 + gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec ); + gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec ); +#endif + +//JRMMERGE_GRAVAXIS: This changed to much to merge, see if you can get your monsters on planets changes back in here. I'll leave both versions +#if OLD_CODE + GetGravViewAxis().ProjectVector( dir, localDir ); // HUMANHEAD JRM: VIEWAXIS_TO_GETGRAVVIEWAXIS + lookAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + lookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + lookAng.roll = 0.0f; +#else + // determine pitch from joint position + dir = focusPos - eyepos; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 ); + } + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch; + newLookAng.roll = 0.0f; +#endif + + diff = newLookAng - lookAng; + + if ( eyeAng != diff ) { + eyeAng = diff; + eyeAng.Clamp( eyeMin, eyeMax ); + idAngles angDelta = diff - eyeAng; + if ( !angDelta.Compare( ang_zero, 0.1f ) ) { + alignHeadTime = gameLocal.time; + } else { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + } + } + + if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { + alignHeadTime = gameLocal.time; + } + + if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + destLookAng = newLookAng; + destLookAng.Clamp( lookMin, lookMax ); + } + + diff = destLookAng - lookAng; + if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { + if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { + diff.pitch = 360.0f - diff.pitch; + } + } + if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { + if ( diff.yaw > 180.0f ) { + diff.yaw -= 360.0f; + } else if ( diff.yaw <= -180.0f ) { + diff.yaw += 360.0f; + } + } + lookAng = lookAng + diff * headFocusRate; + lookAng.Normalize180(); + + jointAng.roll = 0.0f; + if ( allowJointMod ) { + 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() ); + } + } + + if ( move.moveType == MOVETYPE_FLY ) { + // lean into turns + AdjustFlyingAngles(); + } + + if ( headEnt ) { + idAnimator *headAnimator = headEnt->GetAnimator(); + + // HUMANHEAD pdm: Added support for look joints in head entities + if ( allowJointMod ) { + for( i = 0; i < headLookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw; + headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + // HUMANHEAD END + + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); + headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); + + //if ( ai_debugMove.GetBool() ) { + // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec ); + //} + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); + animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); + animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + + //HUMANHEAD pdm jawflap + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + //END HUMANHEAD + + //update guns + //HUMANHEAD jsh PCF 4/27/06 no gun tracking + //if ( enemy.IsValid() ) { + // if ( AI_GUN_TRACKING && !AI_DEAD && !AI_RECHARGING ) { + // idVec3 focusPos, bonePos; + // idMat3 boneAxis; + + // idAngles ang = ang_zero; + // ang.pitch = (bonePos - enemy->GetOrigin()).ToPitch(); + // ang.pitch += gunShake.pitch; + // ang.yaw = (targetCurrent_L-bonePos).ToYaw() - viewAxis.ToAngles().yaw; + // ang.yaw += gunShake.yaw; + // GetJointWorldTransform( spawnArgs.GetString( "gun_bone_left" ), bonePos, boneAxis ); + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_left" ) ), JOINTMOD_WORLD, idAngles( (bonePos - targetCurrent_L).ToPitch(), (targetCurrent_L-bonePos).ToYaw() - viewAxis.ToAngles().yaw, 0.0f ).ToMat3() ); + + // ang.yaw = (targetCurrent_R-bonePos).ToYaw() - viewAxis.ToAngles().yaw; + // ang.yaw += gunShake.yaw; + // GetJointWorldTransform( spawnArgs.GetString( "gun_bone_right" ), bonePos, boneAxis ); + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_right" ) ), JOINTMOD_WORLD, idAngles( (bonePos - targetCurrent_R).ToPitch(), (targetCurrent_R-bonePos).ToYaw() - viewAxis.ToAngles().yaw, 0.0f ).ToMat3() ); + // } else { + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_left" ) ), JOINTMOD_WORLD, mat3_identity ); + // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_right" ) ), JOINTMOD_WORLD, mat3_identity ); + // } + //} + + return true; +} + +void hhCreatureX::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + HandleNoGore(); + + //stop sparking upon death + CancelEvents( &MA_SparkLeft ); + CancelEvents( &MA_SparkRight ); + SAFE_REMOVE( preLaserLeft ); + SAFE_REMOVE( preLaserRight ); + AI_LEFT_DAMAGED = false; + AI_RIGHT_DAMAGED = false; + + if ( laserLeft.IsValid() ) { + laserLeft->Hide(); + } + if ( laserRight.IsValid() ) { + laserRight->Hide(); + } + + if ( spawnArgs.GetBool( "use_death_point", "0" ) ) { + if ( !AI_DEAD ) { + fl.takedamage = false; + AI_DEAD = true; + state = GetScriptFunction( "state_Predeath" ); + SetState( state ); + SetWaitState( "" ); + return; + } + } + + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + + fl.takedamage = false; + idAngles ang; + const char *modelDeath; + + // make sure the monster is activated + EndAttack(); + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + GetDamageGroup( location ) ); + } + + if ( inflictor ) { + AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" ); + } else { + AI_SPECIAL_DAMAGE = 0; + } + + if ( AI_DEAD ) { + AI_PAIN = true; + AI_DAMAGE = true; + return; + } + + // stop all voice sounds + StopSound( SND_CHANNEL_VOICE, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false ); + head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + } + + disableGravity = false; + move.moveType = MOVETYPE_DEAD; + af_push_moveables = false; + + physicsObj.UseFlyMove( false ); + physicsObj.ForceDeltaMove( false ); + + // end our looping ambient sound + StopSound( SND_CHANNEL_AMBIENT, false ); + + if ( attacker && attacker->IsType( idActor::Type ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + } + + // activate targets + ActivateTargets( attacker ); + + RemoveAttachments(); + RemoveProjectile(); + StopMove( MOVE_STATUS_DONE ); + + ClearEnemy(); + AI_DEAD = true; + + // HUMANHEAD jsh commented out for girlfriendx + // make monster nonsolid + if ( spawnArgs.GetBool( "boss" ) ) { + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + } + + Unbind(); + + // spawn death clip model + if ( spawnArgs.GetBool( "boss" ) ) { + idDict dict; + const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_deathclip")); + dict.Copy(*torsoDict); + dict.SetVector("origin", GetOrigin() + modelOffset * GetAxis() ); + dict.SetMatrix("rotation", GetAxis()); + idEntity *e; + gameLocal.SpawnEntityDef(dict, &e); + if ( e ) { + e->GetPhysics()->SetContents( CONTENTS_PLAYERCLIP ); + } + } + + if ( StartRagdoll() ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + // HUMANHEAD JRM - some monsters are removed, but always need to play sound + } else if(spawnArgs.GetBool("death_sound_always","0")) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + // HUMANHEAD JRM - end + + 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 + 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(); + } + + restartParticles = false; + + state = GetScriptFunction( "state_Killed" ); + SetState( state ); + SetWaitState( "" ); + + if ( attacker && attacker->IsType( idPlayer::Type ) ) { + static_cast< idPlayer* >( attacker )->AddAIKill(); + } + + // General non-item dropping (for monsters, souls, etc.) + const idKeyValue *kv = NULL; + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while ( kv ) { + + idStr drops = kv->GetValue(); + idDict args; + + idStr last5 = kv->GetKey().Right(5); + if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) { + args.Set( "classname", drops ); + + // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up + args.Set("monsterSpawnedBy", name.c_str()); + + idVec3 origin; + idMat3 axis; + idStr jointKey = kv->GetKey() + idStr("Joint"); + idStr jointName = spawnArgs.GetString( jointKey ); + idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint"); + idStr j2jName = spawnArgs.GetString( joint2JointKey ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( args, &newEnt ); + HH_ASSERT(newEnt != NULL); + + if(jointName.Length()) { + jointHandle_t joint = GetAnimator()->GetJointHandle( jointName ); + if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name ); + origin = renderEntity.origin; + axis = renderEntity.axis; + } + axis *= renderEntity.axis; + origin = renderEntity.origin + origin * renderEntity.axis; + newEnt->SetAxis(axis); + newEnt->SetOrigin(origin); + } + else { + + newEnt->SetAxis(viewAxis); + newEnt->SetOrigin(GetOrigin()); + } + + } + + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +/* +===================== +hhCreatureX::Save +===================== +*/ +void hhCreatureX::Save( idSaveGame *savefile ) const { + laserRight.Save( savefile ); + laserLeft.Save( savefile ); + savefile->WriteBool( bLaserLeftActive ); + savefile->WriteBool( bLaserRightActive ); + + int i, num = leftBeamList.Num(); + savefile->WriteInt( num ); + for ( i = 0; i < num; i++ ) { + leftBeamList[i].Save( savefile ); + } + + num = rightBeamList.Num(); + savefile->WriteInt( num ); + for ( i = 0; i < num; i++ ) { + rightBeamList[i].Save( savefile ); + } + + savefile->WriteInt( numBurstBeams ); + savefile->WriteInt( leftGunHealth ); + savefile->WriteInt( rightGunHealth ); + savefile->WriteAngles( gunShake ); + savefile->WriteInt( nextBeamTime ); + savefile->WriteInt( nextLeftZapTime ); + savefile->WriteInt( nextRightZapTime ); + savefile->WriteInt( leftGunLives ); + savefile->WriteInt( rightGunLives ); + + savefile->WriteVec3( targetStart_L ); + savefile->WriteVec3( targetEnd_L ); + savefile->WriteVec3( targetCurrent_L ); + savefile->WriteVec3( targetStart_R ); + savefile->WriteVec3( targetEnd_R ); + savefile->WriteVec3( targetCurrent_R ); + + savefile->WriteFloat( targetAlpha_L ); + savefile->WriteFloat( targetAlpha_R ); + + savefile->WriteVec3( laserEndLeft ); + savefile->WriteVec3( laserEndRight ); + + savefile->WriteInt( nextLaserLeft ); + savefile->WriteInt( nextLaserRight ); + savefile->WriteInt( nextHealthTick ); + + leftRecharger.Save( savefile ); + rightRecharger.Save( savefile ); + + leftRechargeBeam.Save( savefile ); + rightRechargeBeam.Save( savefile ); + + leftDamageBeam.Save( savefile ); + rightDamageBeam.Save( savefile ); + + leftRetractBeam.Save( savefile ); + rightRetractBeam.Save( savefile ); + + preLaserLeft.Save( savefile ); + preLaserRight.Save( savefile ); + + muzzleLeftFx.Save( savefile ); + muzzleRightFx.Save( savefile ); + impactLeftFx.Save( savefile ); + impactRightFx.Save( savefile ); + rechargeLeftFx.Save( savefile ); + rechargeRightFx.Save( savefile ); + retractLeftFx.Save( savefile ); + retractRightFx.Save( savefile ); + + savefile->WriteInt( leftRechargerHealth ); + savefile->WriteInt( rightRechargerHealth ); + savefile->WriteBool( bScripted ); +} + +/* +===================== +hhCreatureX::Restore +===================== +*/ +void hhCreatureX::Restore( idRestoreGame *savefile ) { + laserRight.Restore( savefile ); + laserLeft.Restore( savefile ); + savefile->ReadBool( bLaserLeftActive ); + savefile->ReadBool( bLaserRightActive ); + + int i, num; + savefile->ReadInt( num ); + leftBeamList.SetNum( num ); + for ( i = 0; i < num; i++ ) { + leftBeamList[i].Restore( savefile ); + } + + savefile->ReadInt( num ); + rightBeamList.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + rightBeamList[i].Restore( savefile ); + } + + savefile->ReadInt( numBurstBeams ); + savefile->ReadInt( leftGunHealth ); + savefile->ReadInt( rightGunHealth ); + savefile->ReadAngles( gunShake ); + savefile->ReadInt( nextBeamTime ); + savefile->ReadInt( nextLeftZapTime ); + savefile->ReadInt( nextRightZapTime ); + savefile->ReadInt( leftGunLives ); + savefile->ReadInt( rightGunLives ); + + savefile->ReadVec3( targetStart_L ); + savefile->ReadVec3( targetEnd_L ); + savefile->ReadVec3( targetCurrent_L ); + savefile->ReadVec3( targetStart_R ); + savefile->ReadVec3( targetEnd_R ); + savefile->ReadVec3( targetCurrent_R ); + + savefile->ReadFloat( targetAlpha_L ); + savefile->ReadFloat( targetAlpha_R ); + + savefile->ReadVec3( laserEndLeft ); + savefile->ReadVec3( laserEndRight ); + + savefile->ReadInt( nextLaserLeft ); + savefile->ReadInt( nextLaserRight ); + savefile->ReadInt( nextHealthTick ); + + leftRecharger.Restore( savefile ); + rightRecharger.Restore( savefile ); + + leftRechargeBeam.Restore( savefile ); + rightRechargeBeam.Restore( savefile ); + + leftDamageBeam.Restore( savefile ); + rightDamageBeam.Restore( savefile ); + + leftRetractBeam.Restore( savefile ); + rightRetractBeam.Restore( savefile ); + + preLaserLeft.Restore( savefile ); + preLaserRight.Restore( savefile ); + + muzzleLeftFx.Restore( savefile ); + muzzleRightFx.Restore( savefile ); + impactLeftFx.Restore( savefile ); + impactRightFx.Restore( savefile ); + rechargeLeftFx.Restore( savefile ); + rechargeRightFx.Restore( savefile ); + retractLeftFx.Restore( savefile ); + retractRightFx.Restore( savefile ); + + savefile->ReadInt( leftRechargerHealth ); + savefile->ReadInt( rightRechargerHealth ); + savefile->ReadBool( bScripted ); +} + + +void hhCreatureX::MuzzleLeftOn() { + if ( !muzzleLeftFx.IsValid() ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_muzzle"), spawnArgs.GetString("muzzle_bone_left"), NULL, &MA_AssignLeftMuzzleFx, false ); + } + if ( !impactLeftFx.IsValid() ) { + BroadcastFxInfo( spawnArgs.GetString("fx_impact"), GetOrigin(), GetAxis(), NULL, &MA_AssignLeftImpactFx, false ); + } +} + +void hhCreatureX::MuzzleLeftOff() { + SAFE_REMOVE( muzzleLeftFx ); + SAFE_REMOVE( impactLeftFx ); +} + +void hhCreatureX::Event_AssignLeftMuzzleFx( hhEntityFx* fx ) { + muzzleLeftFx = fx; +} + +void hhCreatureX::Event_AssignRightMuzzleFx( hhEntityFx* fx ) { + muzzleRightFx = fx; +} + +void hhCreatureX::Event_AssignRightImpactFx( hhEntityFx* fx ) { + impactRightFx = fx; +} + +void hhCreatureX::Event_AssignLeftImpactFx( hhEntityFx* fx ) { + impactLeftFx = fx; +} + + +void hhCreatureX::Event_AssignLeftRechargeFx( hhEntityFx* fx ) { + rechargeLeftFx = fx; +} + +void hhCreatureX::Event_AssignRightRechargeFx( hhEntityFx* fx ) { + rechargeRightFx= fx; +} + +void hhCreatureX::MuzzleRightOn() { + if ( !muzzleRightFx.IsValid() ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_muzzle"), spawnArgs.GetString("muzzle_bone_right"), NULL, &MA_AssignRightMuzzleFx, false ); + } + if ( !impactRightFx.IsValid() ) { + BroadcastFxInfo( spawnArgs.GetString("fx_impact"), GetOrigin(), GetAxis(), NULL, &MA_AssignRightImpactFx, false ); + } +} + +void hhCreatureX::MuzzleRightOff() { + SAFE_REMOVE( muzzleRightFx ); + SAFE_REMOVE( impactRightFx ); +} + +void hhCreatureX::Event_SetGunOffset( const idAngles &ang ) { + gunShake = ang; +} + +void hhCreatureX::Event_EndLeftBeams() { + for ( int i = 0; i < numBurstBeams; i ++ ) { + if( leftBeamList[i].IsValid() ) { + leftBeamList[i]->Activate( false ); + } + } +} + +void hhCreatureX::Event_EndRightBeams() { + for ( int i = 0; i < numBurstBeams; i ++ ) { + if( rightBeamList[i].IsValid() ) { + rightBeamList[i]->Activate( false ); + } + } +} + +void hhCreatureX::Event_StartRechargeBeams() { + StartSound( "snd_recharge_beam_start", SND_CHANNEL_ANY ); + if ( leftRecharger.IsValid() ) { + const char *defName = spawnArgs.GetString( "fx_recharge_beam_start" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + idEntityFx *rechargeFx = SpawnFxLocal( defName, leftRecharger->GetOrigin(), leftRecharger->GetAxis(), &fxInfo, gameLocal.isClient ); + if ( rechargeFx ) { + rechargeFx->Bind( leftRecharger.GetEntity(), true ); + } + } + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( true ); + leftRechargeBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + } + if ( rightRecharger.IsValid() ) { + const char *defName = spawnArgs.GetString( "fx_recharge_beam_start" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + idEntityFx *rechargeFx = SpawnFxLocal( defName, rightRecharger->GetOrigin(), rightRecharger->GetAxis(), &fxInfo, gameLocal.isClient ); + if ( rechargeFx ) { + rechargeFx->Bind( rightRecharger.GetEntity(), true ); + } + } + + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( true ); + rightRechargeBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + } +} + +void hhCreatureX::Event_GunRecharge( int onOff ) { + if ( onOff ) { + idVec3 boneOrigin; + idMat3 boneAxis; + GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis ); + BroadcastFxInfo( spawnArgs.GetString("fx_recharger_enter"), boneOrigin, boneAxis, NULL, NULL, false ); + + GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis ); + BroadcastFxInfo( spawnArgs.GetString("fx_recharger_enter"), boneOrigin, boneAxis, NULL, NULL, false ); + + PostEventSec( &MA_StartRechargeBeams, spawnArgs.GetFloat( "recharge_delay", "0.9" ) ); + if ( leftRecharger.IsValid() ) { + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + leftRechargerHealth = leftRecharger->GetHealth(); + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + leftRecharger->Show(); + leftRecharger->SetOrigin( GetOrigin() ); + idVec3 offset = spawnArgs.GetVector( "healer_offset", "0 80 60" ); + leftRecharger->MoveToPosition( GetOrigin() + viewAxis * offset ); + leftRecharger->SetState( leftRecharger->GetScriptFunction( "state_Healer" ) ); + leftRecharger->SetWaitState( "" ); + } + if ( rightRecharger.IsValid() ) { + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + rightRechargerHealth = rightRecharger->GetHealth(); + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) ); + } + rightRecharger->Show(); + idVec3 offset = spawnArgs.GetVector( "healer_offset", "0 80 60" ); + offset.y *= -1; + rightRecharger->SetOrigin( GetOrigin() ); + rightRecharger->MoveToPosition( GetOrigin() + viewAxis * offset ); + rightRecharger->SetState( rightRecharger->GetScriptFunction( "state_Healer" ) ); + rightRecharger->SetWaitState( "" ); + } + if ( !AI_LEFT_DAMAGED ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_weakpoint"), spawnArgs.GetString("recharge_bone_left"), NULL, &MA_AssignLeftRechargeFx, false ); + } + if ( !AI_RIGHT_DAMAGED ) { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_weakpoint"), spawnArgs.GetString("recharge_bone_right"), NULL, &MA_AssignRightRechargeFx, false ); + } + } else { + SAFE_REMOVE( rechargeLeftFx ); + SAFE_REMOVE( rechargeRightFx ); + } +} + +void hhCreatureX::Event_EndRecharge() { + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Activate( false ); + } + if ( leftRecharger.IsValid() ) { + leftRecharger->SetState( leftRecharger->GetScriptFunction( "state_Return" ) ); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Activate( false ); + } + if ( rightRecharger.IsValid() ) { + rightRecharger->SetState( rightRecharger->GetScriptFunction( "state_Return" ) ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Activate( false ); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Activate( false ); + } + CancelEvents( &MA_ResetRechargeBeam ); + CancelEvents( &MA_StartRechargeBeams ); +} + +//HUMANHEAD jsh PCF 4/27/06 initialized proj and made sure ReturnEntity is called +void hhCreatureX::Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) { + idProjectile *proj = NULL; + + // Bonedir launch? + if((BOOL)boneDir) { + proj = hhProjectile::SpawnProjectile(projDef); + if ( proj ) { + idMat3 axis; + idVec3 muzzle; + GetMuzzle( jointname, muzzle, axis ); + proj->Create(this, muzzle, axis); + proj->Launch(muzzle, axis, vec3_zero); + } + } + else { + if ( shootTarget.IsValid() ) { + proj = LaunchProjectile( jointname, shootTarget.GetEntity(), false, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } else { + proj = LaunchProjectile( jointname, enemy.GetEntity(), false, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } + } + + idThread::ReturnEntity( proj ); +} + +void hhCreatureX::Event_SparkLeft() { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_spark"), spawnArgs.GetString("damage_bone_left"), NULL ); + PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); +} + +void hhCreatureX::Event_SparkRight() { + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_spark"), spawnArgs.GetString("damage_bone_right"), NULL ); + PostEventSec(&MA_SparkRight, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() ); +} + +void hhCreatureX::Event_HudEvent( const char* eventName ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( eventName ); + gameLocal.GetLocalPlayer()->hud->StateChanged(gameLocal.time); + } +} + +void hhCreatureX::Activate( idEntity *activator ) { + if ( preLaserLeft.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().z)); //Test for bad origin + } + if ( preLaserRight.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().z)); //Test for bad origin + } + if ( laserLeft.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().z)); //Test for bad origin + } + if ( laserRight.IsValid() ) { + HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().x)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().y)); //Test for bad origin + HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().z)); //Test for bad origin + } + + hhMonsterAI::Activate( activator ); +} + +void hhCreatureX::Show() { + hhMonsterAI::Show(); + if ( preLaserLeft.IsValid() ) { + preLaserLeft->Hide(); + preLaserLeft->Activate( false ); + } + if ( preLaserRight.IsValid() ) { + preLaserRight->Hide(); + preLaserRight->Activate( false ); + } + if ( laserLeft.IsValid() ) { + laserLeft->Hide(); + laserLeft->Activate( false ); + } + if ( laserRight.IsValid() ) { + laserRight->Hide(); + laserRight->Activate( false ); + } + if ( leftRechargeBeam.IsValid() ) { + leftRechargeBeam->Hide(); + leftRechargeBeam->Activate( false ); + } + if ( rightRechargeBeam.IsValid() ) { + rightRechargeBeam->Hide(); + rightRechargeBeam->Activate( false ); + } + if ( leftDamageBeam.IsValid() ) { + leftDamageBeam->Hide(); + leftDamageBeam->Activate( false ); + } + if ( rightDamageBeam.IsValid() ) { + rightDamageBeam->Hide(); + rightDamageBeam->Activate( false ); + } + if ( leftRetractBeam.IsValid() ) { + leftRetractBeam->Hide(); + leftRetractBeam->Activate( false ); + } + if ( rightRetractBeam.IsValid() ) { + rightRetractBeam->Hide(); + rightRetractBeam->Activate( false ); + } +} + +void hhCreatureX::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( spawnArgs.GetBool( "boss" ) ) { + if ( attacker && attacker->IsType( idPlayer::Type ) && idStr::Icmp( damageDefName, spawnArgs.GetString( "def_damageTelefrag", "damage_telefrag" ) ) == 0 ) { + //telefragged so move somewhere nice + SetState( GetScriptFunction( "state_Telefragged" ) ); + return; + } + } + + if ( !fl.takedamage ) { + return; + } + //check for splash damage or jenny-specific damage based on location + if ( AI_DEAD && spawnArgs.GetBool( "boss" ) ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( ( damageDef && damageDef->GetFloat( "radius" ) > 0 ) || + ( location && strcmp( GetDamageGroup( location ), "jenny" ) == 0 ) ) { + SetState( GetScriptFunction( "state_RealDeath" ) ); + fl.takedamage = false; + } + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_creaturex.h b/neo/prey/Prey/ai_creaturex.h new file mode 100644 index 00000000..827173ad --- /dev/null +++ b/neo/prey/Prey/ai_creaturex.h @@ -0,0 +1,139 @@ + +#ifndef __PREY_AI_CREATURE_H__ +#define __PREY_AI_CREATURE_H__ + + +class hhCreatureX : public hhMonsterAI { + +public: + + CLASS_PROTOTYPE(hhCreatureX); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_GunRecharge( int onOff ) {}; + void Event_LaserOn() {}; + void Event_LaserOff() {}; + void Event_UpdateLasers() {}; + void Event_SetGunOffset( const idAngles &ang ) {}; + void Event_EndLeftBeams() {}; + void Event_EndRightBeams() {}; + void Event_AssignRightMuzzleFx( hhEntityFx* fx ) {}; + void Event_AssignLeftMuzzleFx( hhEntityFx* fx ) {}; + void Event_AssignRightImpactFx( hhEntityFx* fx ) {}; + void Event_AssignLeftImpactFx( hhEntityFx* fx ) {}; + void Event_AssignLeftRechargeFx( hhEntityFx* fx ) {}; + void Event_AssignRightRechargeFx( hhEntityFx* fx ) {}; + void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) {}; + void Event_HudEvent( const char *eventName ) {}; + void Event_RechargeHealth() {}; + void Event_EndRecharge() {}; + void Event_ResetRechargeBeam() {}; + void Event_SparkLeft() {}; + void Event_SparkRight() {}; + void Event_LeftGunDeath() {}; + void Event_RightGunDeath() {}; + void Event_StartRechargeBeams() {}; +#else + + ~hhCreatureX(); + void Spawn(); + void LinkScriptVariables( void ); + void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + idScriptBool AI_GUN_TRACKING; + idScriptBool AI_RECHARGING; + idScriptBool AI_LEFT_FIRE; + idScriptBool AI_RIGHT_FIRE; + idScriptBool AI_LEFT_DAMAGED; + idScriptBool AI_RIGHT_DAMAGED; + idScriptBool AI_FIRING_LASER; + idScriptBool AI_HEALTH_TICK; + idScriptBool AI_GUN_EXPLODE; +protected: + void Event_GunRecharge( int onOff ); + void Event_LaserOn(); + void Event_LaserOff(); + void Event_UpdateLasers(); + void Event_SetGunOffset( const idAngles &ang ); + void Event_EndLeftBeams(); + void Event_EndRightBeams(); + void Event_AssignRightMuzzleFx( hhEntityFx* fx ); + void Event_AssignLeftMuzzleFx( hhEntityFx* fx ); + void Event_AssignRightImpactFx( hhEntityFx* fx ); + void Event_AssignLeftImpactFx( hhEntityFx* fx ); + void Event_AssignLeftRechargeFx( hhEntityFx* fx ); + void Event_AssignRightRechargeFx( hhEntityFx* fx ); + void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ); + void Event_HudEvent( const char *eventName ); + void Event_RechargeHealth(); + void Event_EndRecharge(); + void Event_ResetRechargeBeam(); + void Event_SparkLeft(); + void Event_SparkRight(); + void Event_LeftGunDeath(); + void Event_RightGunDeath(); + void Event_StartRechargeBeams(); + + void MuzzleLeftOn(); + void MuzzleRightOn(); + void MuzzleLeftOff(); + void MuzzleRightOff(); + bool UpdateAnimationControllers( void ); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Show(); + void Activate( idEntity *activator ); + idEntityPtr laserRight; + idEntityPtr laserLeft; + idEntityPtr preLaserRight; + idEntityPtr preLaserLeft; + idList< idEntityPtr > leftBeamList; + idList< idEntityPtr > rightBeamList; + idEntityPtr leftRechargeBeam; + idEntityPtr rightRechargeBeam; + idEntityPtr leftDamageBeam; + idEntityPtr rightDamageBeam; + idEntityPtr leftRetractBeam; + idEntityPtr rightRetractBeam; + idEntityPtr muzzleLeftFx; + idEntityPtr muzzleRightFx; + idEntityPtr impactLeftFx; + idEntityPtr impactRightFx; + idEntityPtr rechargeLeftFx; + idEntityPtr rechargeRightFx; + idEntityPtr retractLeftFx; + idEntityPtr retractRightFx; + int numBurstBeams; + bool bLaserLeftActive; + bool bLaserRightActive; + idVec3 targetStart_L; + idVec3 targetEnd_L; + idVec3 targetCurrent_L; + idVec3 targetStart_R; + idVec3 targetEnd_R; + idVec3 targetCurrent_R; + float targetAlpha_L; + float targetAlpha_R; + int leftGunHealth; + int rightGunHealth; + int leftGunLives; + int rightGunLives; + idAngles gunShake; + int nextBeamTime; + int nextLeftZapTime; + int nextRightZapTime; + idVec3 laserEndLeft; + idVec3 laserEndRight; + int nextLaserLeft; + int nextLaserRight; + int nextHealthTick; + int leftRechargerHealth; + int rightRechargerHealth; + idEntityPtr leftRecharger; + idEntityPtr rightRecharger; + bool bScripted; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif /* __PREY_AI_CREATURE_H__ */ diff --git a/neo/prey/Prey/ai_droid.cpp b/neo/prey/Prey/ai_droid.cpp new file mode 100644 index 00000000..bb9d7cf8 --- /dev/null +++ b/neo/prey/Prey/ai_droid.cpp @@ -0,0 +1,660 @@ + + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_StartBurst("startBurst"); +const idEventDef EV_EndBurst("endBurst"); +const idEventDef EV_StartStaticBeam("startStaticBeam"); +const idEventDef EV_EndStaticBeam("endStaticBeam"); +const idEventDef EV_StartChargeShot("startChargeShot"); +const idEventDef EV_EndChargeShot("endChargeShot"); +const idEventDef EV_StartPathing("startPathing"); +const idEventDef EV_EndPathing("endPathing"); +const idEventDef EV_EndZipBeams(""); +const idEventDef EV_HealerReset(""); + +CLASS_DECLARATION(hhMonsterAI, hhDroid) + EVENT(EV_StartStaticBeam, hhDroid::Event_StartStaticBeam) + EVENT(EV_EndStaticBeam, hhDroid::Event_EndStaticBeam) + EVENT(EV_StartBurst, hhDroid::Event_StartBurst) + EVENT(EV_EndBurst, hhDroid::Event_EndBurst) + EVENT(EV_StartChargeShot, hhDroid::Event_StartChargeShot) + EVENT(EV_EndChargeShot, hhDroid::Event_EndChargeShot) + EVENT(EV_StartPathing, hhDroid::Event_StartPathing) + EVENT(EV_EndPathing, hhDroid::Event_EndPathing) + EVENT(EV_EndZipBeams, hhDroid::Event_EndZipBeams) + EVENT(EV_HealerReset, hhDroid::Event_HealerReset) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +hhDroid::hhDroid() { + burstLength = 0.0; + burstSpread = 0.0; + burstDuration = 0.0; + staticDuration = 0.0; + staticRange = 0.0; + numBurstBeams = 0; + beamOffset = vec3_zero; + chargeShotSize = 0.0; + old_fly_bob_strength = 0.0; + spinAngle = 0.0f; +} + +hhDroid::~hhDroid() { + for ( int i = 0; i < numBurstBeams; i ++ ) { + SAFE_REMOVE( beamBurstList[i] ); + } +} + +void hhDroid::Show( void ) { + hhMonsterAI::Show(); + + PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) ); +} + +void hhDroid::Spawn(void) +{ + Event_SetMoveType(MOVETYPE_FLY); + lives = spawnArgs.GetInt( "lives", "3" ); + numBurstBeams = spawnArgs.GetInt( "numBurstBeams", "5" ); + burstLength = spawnArgs.GetFloat( "burstLength", "250" ); + staticDuration = spawnArgs.GetFloat( "staticDuration", "2.0" ); + burstDuration = spawnArgs.GetFloat( "burstDuration", "2.0" ); + beamOffset = spawnArgs.GetVector( "beamOffset", "0 0 0" ); + burstSpread = spawnArgs.GetFloat( "burstSpread", "1.0" ); + enemyRange = spawnArgs.GetFloat( "enemy_range", "3000" ); + flyDampening = spawnArgs.GetFloat( "fly_dampening", "0.01" ); + bHealer = spawnArgs.GetBool( "healer" ); + + beamZip = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamZip" ) ); + if( beamZip.IsValid() ) { + beamZip->Activate( false ); + beamZip->SetOrigin( GetPhysics()->GetOrigin() + beamOffset ); + beamZip->Bind( this, false ); + } + + for ( int i = 0; i < numBurstBeams; i ++ ) { + if ( gameLocal.random.RandomFloat() > 0.5f ) { + beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst1" ) ) ); + } else { + beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst2" ) ) ); + } + if( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( false ); + beamBurstList[i]->SetOrigin( GetPhysics()->GetOrigin() + beamOffset ); + beamBurstList[i]->Bind( this, false ); + } + } + + PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) ); +} + +bool hhDroid::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool bPain = hhMonsterAI::Pain( inflictor, attacker, damage, dir, location ); + if ( !bPain ) { + return bPain; + } + + //activate some beams for damage fx + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( true ); + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) ); + PostEventSec(&EV_EndBurst, staticDuration); + } + } + return bPain; +} + +void hhDroid::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + + //update burst beams + idMat3 dir; + if ( enemy.IsValid() ) { + dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3(); + } else { + dir = GetPhysics()->GetAxis(); + } + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() && beamBurstList[i]->IsActivated() ) { + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) ); + } + } + + if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) { + beamBurstList[0]->SetTargetLocation( staticPoint->GetPhysics()->GetOrigin() ); + } + + if ( chargeShot.IsValid() ) { + chargeShot->SetShaderParm( 9, 1 ); + chargeShot->SetShaderParm( 10, chargeShotSize ); + chargeShotSize += spawnArgs.GetFloat( "chargeSizeDelta" ); + chargeShot->GetPhysics()->DisableClip(); + } +} + +void hhDroid::Event_StartChargeShot(void) { + //spawn chargeshot projectile + const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chargeshot") ); + if ( projectileDef ) { + chargeShot = hhProjectile::SpawnProjectile( projectileDef ); + if ( chargeShot.IsValid() ) { + chargeShotSize = spawnArgs.GetFloat( "chargeSizeStart", "1.0" ); + idVec3 launchStart = GetPhysics()->GetOrigin() + spawnArgs.GetVector( "chargeOffset", "15 0 0" ) * GetRenderEntity()->axis; + chargeShot->Create(this, launchStart, GetRenderEntity()->axis); + chargeShot->Launch(chargeShot->GetPhysics()->GetOrigin(), chargeShot->GetPhysics()->GetAxis(), vec3_zero ); + chargeShot->Bind(this, true); + } + } +} + +void hhDroid::Event_EndChargeShot(void) { + //launch projectile + if ( chargeShot.IsValid() ) { + chargeShot->Unbind(); + chargeShot->StartTracking(); + chargeShot.Clear(); + } +} + +void hhDroid::Event_StartBurst(void) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_burstdamage") ); + + //burst effect + idMat3 dir; + if ( enemy.IsValid() ) { + dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3(); + } else { + dir = GetPhysics()->GetAxis(); + } + for ( int i = 0; i < beamBurstList.Num(); i++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( true ); + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) ); + } + } + PostEventSec(&EV_EndBurst, burstDuration); +} + +void hhDroid::Event_EndBurst(void) { + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( false ); + } + } +} + +void hhDroid::Event_EndStaticBeam(void) { + if ( beamBurstList[0].IsValid() ) { + beamBurstList[0]->Activate( false ); + } + if ( staticPoint.IsValid() ) { + staticPoint.Clear(); + } + + //wait random time until next static beam + if ( !IsHidden() && !AI_DEAD ) { + PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) ); + } +} + +void hhDroid::Event_StartStaticBeam(void) { + idVec3 targetPoint; + idEntity *entityList[ MAX_GENTITIES ]; + + if ( IsHidden() || AI_DEAD ) { + return; + } + + //find nearest staticPoint + float radius = spawnArgs.GetFloat( "staticRange" ); + int listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES ); + for( int i = 0; i < listedEntities; i++ ) { + if ( entityList[ i ] && entityList[ i ]->spawnArgs.GetInt( "droidBeam", "0" ) ) { + staticPoint = entityList[ i ]; + break; + } + } + + //activate one beam + if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) { + StartSound( "snd_staticBeam", SND_CHANNEL_BODY, 0, true, NULL ); + beamBurstList[0]->Activate( true ); + beamBurstList[0]->SetTargetLocation( beamBurstList[0]->GetOrigin() + burstLength * hhUtils::RandomVector() ); + staticPoint->ActivateTargets( this ); + PostEventSec(&EV_EndStaticBeam, staticDuration); + } +} + +void hhDroid::FlyTurn( void ) { + if ( AI_PATHING ) { + hhMonsterAI::FlyTurn(); + return; + } + if ( AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); +} + +void hhDroid::Event_StartPathing(void) { + AI_PATHING = true; + old_fly_bob_strength = fly_bob_strength; + fly_bob_strength = 0.0; + ignore_obstacles = true; +} + +void hhDroid::Event_EndPathing(void) { + AI_PATHING = false; + fly_bob_strength = old_fly_bob_strength; + ignore_obstacles = false; +} + +void hhDroid::FlyMove( void ) { + idVec3 goalPos; + idVec3 oldorigin; + idVec3 newDest; + + AI_BLOCKED = false; + if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { + if ( AI_PATHING ) { + physicsObj.SetLinearVelocity( idVec3(0,0,0) ); + } + StopMove( MOVE_STATUS_DONE ); + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( goalPos ) ) { + move.obstacle = NULL; + } + + if ( move.speed ) { + FlySeekGoal( vel, goalPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) { + float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast(); + if ( dist < enemyRange && enemyRange > 0 ) { + AdjustFlyHeight( vel, goalPos ); + } + } + + AdjustFlySpeed( vel ); + + 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( disableGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + AI_BLOCKED = true; + } + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + 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, goalPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +idProjectile *hhDroid::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { + //jsh overridden to allow per-projectile accuracy + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + if ( projectileDef->GetFloat( "attack_accuracy" ) ) { + attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" ); + } else { + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + } + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + if ( projectileDef->GetFloat( "projectile_spread" ) ) { + projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" ); + } else { + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + } + if ( projectileDef->GetFloat( "num_projectiles" ) ) { + num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" ); + } else { + num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" ); + } + + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( !AI_WALLWALK && clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhDroid::Event_FlyZip() { + idVec3 old_origin = GetOrigin(); + hhMonsterAI::Event_FlyZip(); + if ( old_origin != GetOrigin() ) { + if ( beamZip.IsValid() ) { + beamZip->SetTargetLocation( old_origin ); + } + if ( beamZip.IsValid() ) { + beamZip->Activate( true ); + } + PostEventSec( &EV_EndZipBeams, spawnArgs.GetFloat( "zip_beam_duration", "1.0" ) ); + } +} + +void hhDroid::Event_EndZipBeams() { + if ( beamZip.IsValid() ) { + beamZip->Activate( false ); + } +} + +void hhDroid::Event_HealerReset() { + fl.takedamage = true; +} + +void hhDroid::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( bHealer ) { + lives--; + if ( lives <= 0 ) { + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + } else { + const char *defName = spawnArgs.GetString( "fx_pain" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + idEntityFx *painFx = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient ); + if ( painFx ) { + painFx->Bind( this, true ); + } + } + health = spawnArgs.GetInt( "health" ); + fl.takedamage = false; + PostEventSec( &EV_HealerReset, spawnArgs.GetFloat( "reset_delay", "0.8" ) ); + } + return; + } + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + for ( int i = 0; i < beamBurstList.Num(); i ++ ) { + if ( beamBurstList[i].IsValid() ) { + beamBurstList[i]->Activate( true ); + beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) ); + PostEventSec(&EV_EndBurst, staticDuration); + } + } +} + +void hhDroid::AdjustFlySpeed( idVec3 &vel ) { + float speed; + + // apply dampening + vel -= vel * flyDampening * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if ( move.speed && ( speed > move.speed ) ) { + speed = move.speed; + } + + vel *= speed; +} + +void hhDroid::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + + if ( bHealer ) { + renderEntity.axis = idAngles( 0, 0, spinAngle ).ToMat3() * renderEntity.axis; + spinAngle += 10; + if ( spinAngle > 360.0f ) { + spinAngle = 0.0f; + } + } + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +===================== +hhDroid::Save +===================== +*/ +void hhDroid::Save( idSaveGame *savefile ) const { + int num = beamBurstList.Num(); + savefile->WriteInt( num ); + for ( int i = 0; i < num; i++ ) { + beamBurstList[i].Save( savefile ); + } + + staticPoint.Save( savefile ); + chargeShot.Save( savefile ); + beamZip.Save( savefile ); + + savefile->WriteVec3( savedGravity ); + savefile->WriteFloat( chargeShotSize ); + savefile->WriteFloat( burstLength ); + savefile->WriteFloat( burstSpread ); + savefile->WriteFloat( burstDuration ); + savefile->WriteFloat( staticDuration ); + savefile->WriteFloat( staticRange ); + savefile->WriteInt( numBurstBeams ); + savefile->WriteVec3( beamOffset ); + savefile->WriteFloat( old_fly_bob_strength ); + savefile->WriteFloat( enemyRange ); + savefile->WriteFloat( flyDampening ); + savefile->WriteFloat( spinAngle ); + savefile->WriteBool( bHealer ); + savefile->WriteInt( lives ); +} + +/* +===================== +hhDroid::Restore +===================== +*/ +void hhDroid::Restore( idRestoreGame *savefile ) { + int num; + savefile->ReadInt( num ); + beamBurstList.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + beamBurstList[i].Restore( savefile ); + } + + staticPoint.Restore( savefile ); + chargeShot.Restore( savefile ); + beamZip.Restore( savefile ); + + savefile->ReadVec3( savedGravity ); + savefile->ReadFloat( chargeShotSize ); + savefile->ReadFloat( burstLength ); + savefile->ReadFloat( burstSpread ); + savefile->ReadFloat( burstDuration ); + savefile->ReadFloat( staticDuration ); + savefile->ReadFloat( staticRange ); + savefile->ReadInt( numBurstBeams ); + savefile->ReadVec3( beamOffset ); + savefile->ReadFloat( old_fly_bob_strength ); + savefile->ReadFloat( enemyRange ); + savefile->ReadFloat( flyDampening ); + savefile->ReadFloat( spinAngle ); + savefile->ReadBool( bHealer ); + savefile->ReadInt( lives ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_droid.h b/neo/prey/Prey/ai_droid.h new file mode 100644 index 00000000..427a9856 --- /dev/null +++ b/neo/prey/Prey/ai_droid.h @@ -0,0 +1,73 @@ + +#ifndef __PREY_AI_DROID_H__ +#define __PREY_AI_DROID_H__ + +class hhDroid : public hhMonsterAI { + +public: + + CLASS_PROTOTYPE(hhDroid); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_StartBurst(void) {}; + void Event_EndBurst(void) {}; + void Event_StartStaticBeam(void) {}; + void Event_EndStaticBeam(void) {}; + void Event_StartChargeShot(void) {}; + void Event_EndChargeShot(void) {}; + void Event_StartPathing(void) {}; + void Event_EndPathing(void) {}; + void Event_FlyZip() {}; + void Event_EndZipBeams() {}; + void Event_HealerReset() {} +#else + hhDroid(); + ~hhDroid(); + void Spawn( void ); + virtual void Think( void ); + void FlyTurn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void AdjustFlySpeed( idVec3 &vel ); +protected: + idList< idEntityPtr > beamBurstList; + idEntityPtr beamZip; + idEntityPtr staticPoint; + idEntityPtr chargeShot; + void Event_StartBurst(void); + void Event_EndBurst(void); + void Event_StartStaticBeam(void); + void Event_EndStaticBeam(void); + void Event_StartChargeShot(void); + void Event_EndChargeShot(void); + void Event_StartPathing(void); + void Event_EndPathing(void); + void Event_FlyZip(); + void Event_EndZipBeams(); + void Event_HealerReset(); + idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Show( void ); + void FlyMove( void ); + void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void UpdateModelTransform(); + + idVec3 savedGravity; + float chargeShotSize; + float burstLength; + float burstSpread; + float burstDuration; + float staticDuration; //how long staticbeam lasts + float staticRange; //distance to check for staticbeam points + int numBurstBeams; + idVec3 beamOffset; + float old_fly_bob_strength; + float enemyRange; + float flyDampening; + int lives; + float spinAngle; + bool bHealer; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_gasbag_simple.cpp b/neo/prey/Prey/ai_gasbag_simple.cpp new file mode 100644 index 00000000..ccb9f828 --- /dev/null +++ b/neo/prey/Prey/ai_gasbag_simple.cpp @@ -0,0 +1,801 @@ + + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_AcidBlast("acidBlast"); +const idEventDef EV_AcidDrip(""); +const idEventDef EV_DeathCloud(""); +const idEventDef EV_LaunchPod("launchPod"); +const idEventDef EV_NewPod("", "e"); +const idEventDef EV_SpawnBlastDebris(""); +const idEventDef EV_ChargeEnemy("chargeEnemy"); +const idEventDef EV_GrabEnemy("grabEnemy", NULL, 'f'); +const idEventDef EV_BiteEnemy("biteEnemy"); +const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v"); +const idEventDef EV_BindUnfroze("", "e"); +const idEventDef EV_GrabCheck("grabCheck", "", 'd'); +const idEventDef EV_MoveToGrabPosition("moveToGrabPosition"); +const idEventDef EV_CheckRange("checkRange", "", 'd'); +const idEventDef EV_EnemyRangeZ("enemyRangeZ", "", 'f'); + +CLASS_DECLARATION(hhMonsterAI, hhGasbagSimple) + EVENT(EV_AcidBlast, hhGasbagSimple::Event_AcidBlast) + EVENT(EV_AcidDrip, hhGasbagSimple::Event_AcidDrip) + EVENT(EV_DeathCloud, hhGasbagSimple::Event_DeathCloud) + EVENT(EV_LaunchPod, hhGasbagSimple::Event_LaunchPod) + EVENT(EV_NewPod, hhGasbagSimple::Event_NewPod) + EVENT(EV_SpawnBlastDebris, hhGasbagSimple::Event_SpawnBlastDebris) + EVENT(EV_ChargeEnemy, hhGasbagSimple::Event_ChargeEnemy) + EVENT(EV_GrabEnemy, hhGasbagSimple::Event_GrabEnemy) + EVENT(EV_BiteEnemy, hhGasbagSimple::Event_BiteEnemy) + EVENT(EV_DirectMoveToPosition, hhGasbagSimple::Event_DirectMoveToPosition) + EVENT(EV_BindUnfroze, hhGasbagSimple::Event_BindUnfroze) + EVENT(EV_GrabCheck, hhGasbagSimple::Event_GrabCheck) + EVENT(EV_MoveToGrabPosition, hhGasbagSimple::Event_MoveToGrabPosition) + EVENT(EV_CheckRange, hhGasbagSimple::Event_CheckRange) + EVENT(EV_EnemyRangeZ, hhGasbagSimple::Event_EnemyRangeZ) +END_CLASS + +static const idEventDef EV_Unfreeze( "" ); + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// +// ~hhGasbagSimple() +// +hhGasbagSimple::~hhGasbagSimple(void) { + // Make sure to unbind the player if we've grabbed him to prevent deleting him + if (enemy.IsValid() && enemy->IsBoundTo(this) && enemy->IsType(hhPlayer::Type)) { + enemy->Unbind(); + enemy->PostEventMS(&EV_Unfreeze, 0); + } +} + +// +// Spawn() +// +void hhGasbagSimple::Spawn(void) { + Event_SetMoveType(MOVETYPE_FLY); // So we ignore gravity zones from the beginning + podOffset = spawnArgs.GetVector("pod_offset", "0 0 64"); + podRange = spawnArgs.GetFloat("podRange", "250"); + dripCount = 0; + nextWoundTime = 0; + + bindController = static_cast( gameLocal.SpawnClientObject(spawnArgs.GetString("def_bindController"), NULL) ); + HH_ASSERT( bindController.IsValid() ); + bindController->fl.networkSync = false; + + float yawLimit = spawnArgs.GetFloat("yawlimit", "180"); + const char *handName = spawnArgs.GetString("def_tractorhand"); + const char *animName = spawnArgs.GetString("boundanim"); + + bindController->SetRiderParameters(animName, handName, yawLimit, 0.0f); + bindController->Bind(this, true); + bindController->SetOrigin(spawnArgs.GetVector("grab_offset")); + bindController->fl.neverDormant = true; +} + +// +// Ticker() +// +void hhGasbagSimple::Ticker(void) { + for (int i = 0; i < podList.Num(); i++) { + if (!podList[i].IsValid()) { + podList.RemoveIndex(i); + i--; // This index is gone, so don't increment + continue; + } + } + + // Update the podcount + AI_PODCOUNT = podList.Num(); + + if (podList.Num() == 0) { + BecomeInactive(TH_TICKER); + } +} + +// +// Save() +// +void hhGasbagSimple::Save(idSaveGame *savefile) const { + savefile->WriteInt(dripCount); + + savefile->WriteInt(podList.Num()); + for (int i = 0; i < podList.Num(); i++) { + podList[i].Save(savefile); + } + bindController.Save(savefile); +} + +// +// Restore() +// +void hhGasbagSimple::Restore(idRestoreGame *savefile) { + Spawn(); + savefile->ReadInt(dripCount); + + int num; + savefile->ReadInt(num); + podList.SetNum(num); + for (int i = 0; i < num; i++) { + podList[i].Restore(savefile); + } + bindController.Restore(savefile); + + nextWoundTime = 0; +} + +// +// Event_AcidBlast() +// +void hhGasbagSimple::Event_AcidBlast(void) { + if (health <= 0) { // Don't launch a pod if we're dead + return; + } + + if (!enemy.IsValid()) { + return; + } + + idEntity *target = enemy.GetEntity(); + idVec3 targetOrigin = target->GetOrigin(); + float len, nearest = podRange; + + // See if we have a pod near the enemy. If we do, target it instead. + idEntity *entList[100]; + int num = gameLocal.EntitiesWithinRadius(targetOrigin, podRange, entList, 100); + for (int i = 0; i < num; i++) { + if (!entList[i]->IsType(hhPod::Type)) { + continue; + } + + len = (entList[i]->GetOrigin() - targetOrigin).Length(); + + // Always target the pod nearest to the enemy + if (len < nearest) { + target = entList[i]; + nearest = len; + } + } + + // Spawn a "muzzle flash"-like fx + hhFxInfo fx; + fx.SetEntity(this); + fx.RemoveWhenDone(true); + SpawnFxLocal(spawnArgs.GetString("fx_fire"), GetOrigin() + idVec3(0, 0, -400), enemy->GetPhysics()->GetGravityNormal().ToMat3(), &fx); + + // Spawn an acid blob in the player's general direction + const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_projectile"), false); + LaunchProjectile(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef); + + PostEventMS(&EV_SpawnBlastDebris, 100 + gameLocal.random.RandomInt(100)); + + dripCount = gameLocal.random.RandomInt(3) + 1; + PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Drip acid after firing +} + +// +// Event_SpawnBlastDebris() +// +void hhGasbagSimple::Event_SpawnBlastDebris(void) { + idDict args; + args.Clear(); + args.Set("origin", (GetPhysics()->GetOrigin() + spawnArgs.GetVector("blast_debris_offset", "0 0 128")).ToString()); + gameLocal.SpawnObject("debrisSpawner_gasbag_fire", &args); +} + +// +// Event_AcidDrip() +// +void hhGasbagSimple::Event_AcidDrip(void) { + assert(dripCount > 0); + dripCount--; + + idVec3 target = GetPhysics()->GetOrigin(); + target.z -= 128; + + idVec3 rndVec = hhUtils::RandomVector() * gameLocal.random.RandomInt(20); + const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_drip_projectile", "projectile_acidspray_gasbag"), false); + LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target + rndVec, false, projDef); + + if (dripCount > 0) { + PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Fire again in 200-300ms + } +} + +// +// Event_DeathCloud() +// +void hhGasbagSimple::Event_DeathCloud(void) { + const char *fx = spawnArgs.GetString("fx_death"); + if (!fx || !fx[0]) { + return; + } + hhFxInfo fxInfo; + + fxInfo.RemoveWhenDone(true); + BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo); +} + +// +// Event_LaunchPod() +// +void hhGasbagSimple::Event_LaunchPod() { + if (health <= 0) { // Don't launch a pod if we're dying + return; + } + + idVec3 target, origin, offset = vec3_origin; + if (enemy.IsValid()) { + // Target a random area around the enemy + origin = enemy->GetEyePosition(); + if (gameLocal.random.RandomInt(100) > 30) { // Possibly target the enemy exactly + offset.x = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f; + offset.y = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f; + } + } else { + // Target a random area beneath us + origin = GetOrigin(); + offset.x = gameLocal.random.RandomFloat() * 10.0f; + offset.y = gameLocal.random.RandomFloat() * 10.0f; + offset.z = -10.0f; + } + target = origin + offset; + + const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_pod_projectile", "projectile_gasbag_pod"), false); + LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef); +} + +// +// Event_NewPod +// +void hhGasbagSimple::Event_NewPod(hhPod *pod) { + HH_ASSERT(pod && pod->IsType(hhPod::Type)); + + podList.Append(idEntityPtr(pod)); + + // Update the podcount + AI_PODCOUNT = podList.Num(); + + BecomeActive(TH_TICKER); +} + +// +// Killed +// +void hhGasbagSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + bool wasDead = (AI_DEAD != 0); + + hhMonsterAI::Killed(inflictor, attacker, damage, dir, location); + + if (wasDead) { + return; + } + + CancelEvents(&EV_AcidDrip); + CancelEvents(&EV_AcidBlast); + CancelEvents(&EV_LaunchPod); + dripCount = 0; + + // Release any grabbed enemy + idEntity *rider; + if ((rider = bindController->GetRider()) != NULL) { + bindController->Detach(); + if (rider->IsType(hhPlayer::Type)) { + rider->ProcessEvent(&EV_Unfreeze); + } + } + + int deathAnim = GetAnimator()->GetAnim("death"); + GetAnimator()->PlayAnim(ANIMCHANNEL_TORSO, deathAnim, gameLocal.time, 300.0f); + int ttl = GetAnimator()->AnimLength(deathAnim); + // Freeze + physicsObj.EnableGravity(false); + physicsObj.PutToRest(); + + SetSkinByName( spawnArgs.GetString( "skin_death" ) ); + SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 2500, 8000); // starttime, duration + PostEventSec( &EV_StartSound, 1.5f, "snd_acid", SND_CHANNEL_ANY, 1 ); + + // Spawn gas cloud + PostEventMS(&EV_DeathCloud, ttl); + PostEventMS(&EV_Dispose, 0); +} + +// +// LaunchProjectile +// +idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" ); + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + tmp = target - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + //GetAimDir( muzzle, target, this, dir ); + dir = target - muzzle; + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +// +// LaunchProjectile +// +idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + //float attack_accuracy; + //float attack_cone; + float projectile_spread; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + //attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + //attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" ); + + muzzle = startOrigin; + axis = startAxis; + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhGasbagSimple::LinkScriptVariables(void) { + hhMonsterAI::LinkScriptVariables(); + + LinkScriptVariable(AI_PODCOUNT); + LinkScriptVariable(AI_CHARGEDONE); + LinkScriptVariable(AI_SWOOP); + LinkScriptVariable(AI_DODGEDAMAGE); +} + +// +// Event_GrabEnemy() +// +void hhGasbagSimple::Event_GrabEnemy(void) { + idThread::ReturnFloat( GrabEnemy() ); +} + +// +// GrabEnemy() +// +bool hhGasbagSimple::GrabEnemy(void) { + if (!enemy.IsValid() || enemy->IsBound() || bindController->GetRider()) { + return false; + } + + // Don't grab Talon + if (enemy->IsType(hhTalon::Type)) { + return false; + } + + if (enemy->IsType(hhPlayer::Type)) { + hhPlayer *player = reinterpret_cast (enemy.GetEntity()); + if (player->IsSpiritOrDeathwalking() || player->InGravityZone()) { // Refuse to grab spirit players, or players affected by a gravity zone + return false; + } + player->Freeze(); + } + + bindController->Attach(enemy.GetEntity()); + enemy->SetOrigin(vec3_origin); + return true; +} + +// +// Event_BiteEnemy() +// +void hhGasbagSimple::Event_BiteEnemy(void) { + if (!enemy.IsValid() || bindController->GetRider() != enemy.GetEntity()) { + return; + } + + if (enemy->IsType(hhSpiritProxy::Type)) { + float diff = gameLocal.GetTime() - reinterpret_cast (enemy.GetEntity())->GetActivationTime(); + if (diff < 1150.0f) { + // Don't allow the spirit proxy to ignore damage + scriptThread->WaitMS(diff + 250.0f); + CancelEvents(&EV_BiteEnemy); + PostEventMS(&EV_BiteEnemy, diff + 100.0f); + return; + } + } + + // Direct damage + bindController->Detach(); + enemy->Damage(this, this, vec3_origin, spawnArgs.GetString("def_damage_bite"), 1.0f, INVALID_JOINT); +} + +// +// Damage() +// +void hhGasbagSimple::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) { + int curHealth = health; + idEntity *enemyEnt = enemy.GetEntity(); + hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); + + if (health < curHealth) { + if (gameLocal.time > nextWoundTime && location != INVALID_JOINT) { + hhFxInfo fx; + fx.SetEntity(this); + fx.RemoveWhenDone(true); + SpawnFxLocal(spawnArgs.GetString("fx_damage"), inflictor->GetOrigin(), dir.ToMat3(), &fx); + + nextWoundTime = gameLocal.time + 250; + } + + // Only release enemy if we actually took damage + idEntity *rider; + if ((rider = bindController->GetRider()) != NULL) { + bindController->Detach(); + if (rider->IsType(hhPlayer::Type)) { + rider->PostEventMS(&EV_Unfreeze, 0); + } + } + + AI_DODGEDAMAGE = AI_DODGEDAMAGE + (curHealth - health); + } +} + +// +// Event_DirectMoveToPosition() +// +void hhGasbagSimple::Event_DirectMoveToPosition(const idVec3 &pos) { + StopMove(MOVE_STATUS_DONE); + DirectMoveToPosition(pos); +} + +// +// Event_ChargeEnemy +// +void hhGasbagSimple::Event_ChargeEnemy(void) { + AI_CHARGEDONE = true; + StopMove(MOVE_STATUS_DEST_NOT_FOUND); + if (!enemy.IsValid() || enemy->IsBound()) { + return; + } + + idVec3 enemyOrg; + + fly_offset = 0.0f; + + // position destination so that we're in the enemy's view + enemyOrg = enemy->GetEyePosition(); + enemyOrg -= enemy->GetPhysics()->GetGravityNormal() * fly_offset; + + DirectMoveToPosition(enemyOrg); + HH_ASSERT(move.bEnemyBlocks == false); +} + +void hhGasbagSimple::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + hhMonsterAI::Event_EnemyIsSpirit(player, proxy); + if (bindController->GetRider() == player) { + bindController->Detach(); + player->Unfreeze(); + GrabEnemy(); + } +} + +void hhGasbagSimple::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) { + hhMonsterAI::Event_EnemyIsPhysical(player, proxy); + if (bindController->GetRider() == player) { + player->Freeze(); + } +} + +// +// Event_BindUnfroze +// +void hhGasbagSimple::Event_BindUnfroze(idEntity *unfrozenBind) { + if (!enemy.IsValid() || unfrozenBind != enemy.GetEntity() || bindController->GetRider() != enemy.GetEntity()) { + gameLocal.Warning("Gasbag received BindUnfroze event but enemy is not bound to it.\n"); + return; + } + + bindController->Detach(); +} + +// +// FlyTurn +// +void hhGasbagSimple::FlyTurn(void) { + if (!AI_SWOOP && (AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY)) { + TurnToward( lastVisibleEnemyPos ); + } else if ((move.moveCommand == MOVE_FACE_ENTITY) && move.goalEntity.GetEntity()) { + TurnToward(move.goalEntity.GetEntity()->GetPhysics()->GetOrigin()); + } else if (move.speed > 0.1f) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if (vel.ToVec2().LengthSqr() > 0.1f) { + TurnToward(vel.ToYaw()); + } + } + Turn(); +} + +// +// Event_GrabCheck() +// +void hhGasbagSimple::Event_GrabCheck(void) { + idThread::ReturnInt(enemy.IsValid() && enemy->IsBoundTo(this)); +} + + +void hhGasbagSimple::Event_MoveToGrabPosition(void) { + StopMove(MOVE_STATUS_DEST_NOT_FOUND); + if (!enemy.IsValid() || enemy->IsBound()) { + return; + } + + fly_offset = 0.0f; + + // position destination so that we're right on the enemy when we grab him + DirectMoveToPosition(enemy->GetOrigin() - spawnArgs.GetVector( "grab_offset" )); + HH_ASSERT(move.bEnemyBlocks == false); +} + +void hhGasbagSimple::SetEnemy(idActor *newEnemy) { + idEntity *rider; + if ((rider = bindController->GetRider())) { // This is usually caused by Talon + if (rider->IsType(hhPlayer::Type)) { + rider->PostEventMS(&EV_Unfreeze, 0); + } + bindController->Detach(); + } + + hhMonsterAI::SetEnemy(newEnemy); +} + +void hhGasbagSimple::Event_CheckRange(void) { + if (!enemy.IsValid()) { + idThread::ReturnInt(0); + return; + } + + int retval; + float dist = ( enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + + if (enemy->IsType(hhPlayer::Type)) { + float min = spawnArgs.GetFloat( "dda_range_min" ); + float max = spawnArgs.GetFloat( "dda_range_max" ); + float player_dist = min + (max - min) * gameLocal.GetDDAValue(); + + retval = (int) (dist <= player_dist); + } else { + retval = (int) (dist <= 100.0f); + } + + idThread::ReturnInt(retval); +} + +void hhGasbagSimple::Gib( const idVec3 &dir, const char *damageDefName ) { + // Bypass idActor::Gib() + idAFEntity_Gibbable::Gib( dir, damageDefName ); +} + +int hhGasbagSimple::ReactionTo( const idEntity *ent ) { + //gasbags skip the spirit stuff in hhMonsterAI::ReactionTo() + if ( bNoCombat ) { + return ATTACK_IGNORE; + } + const idActor *actor = static_cast( ent ); + if( actor && actor->IsType(hhDeathProxy::Type) ) { + return ATTACK_IGNORE; + } + if ( ent->IsType( hhMonsterAI::Type ) ) { + const hhMonsterAI *entAI = static_cast( ent ); + if ( entAI && entAI->bNeverTarget ) { + return ATTACK_IGNORE; + } + } + + return idAI::ReactionTo( ent ); +} + +void hhGasbagSimple::Event_EnemyRangeZ( void ) { + idThread::ReturnFloat( lastVisibleEnemyPos.z - GetOrigin().z ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_gasbag_simple.h b/neo/prey/Prey/ai_gasbag_simple.h new file mode 100644 index 00000000..e684865b --- /dev/null +++ b/neo/prey/Prey/ai_gasbag_simple.h @@ -0,0 +1,90 @@ +#ifndef __PREY_AI_GASBAG_SIMPLE_H__ +#define __PREY_AI_GASBAG_SIMPLE_H__ + +extern const idEventDef EV_NewPod; + +class hhPod; + +class hhGasbagSimple : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhGasbagSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_AcidBlast(void) {}; + void Event_AcidDrip(void) {}; + void Event_DeathCloud(void) {}; + void Event_NewPod(hhPod *pod) {}; + void Event_LaunchPod(void) {}; + void Event_SpawnBlastDebris(void) {}; + void Event_ChargeEnemy(void) {}; + void Event_GrabEnemy(void) {}; + void Event_BiteEnemy(void) {}; + void Event_DirectMoveToPosition(const idVec3 &pos) {}; + void Event_BindUnfroze(idEntity *unfrozenBind) {}; + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {}; + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) {}; + void Event_GrabCheck(void) {}; + void Event_MoveToGrabPosition(void) {}; + void Event_CheckRange(void) {}; + void Event_EnemyRangeZ(void) {}; +#else + virtual ~hhGasbagSimple(void); + + void Spawn(void); + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + + virtual void FlyTurn(void); + + virtual void Gib(const idVec3 &dir, const char *damageDefName); + int ReactionTo( const idEntity *ent ); + +protected: + void Event_AcidBlast(void); + void Event_AcidDrip(void); + void Event_DeathCloud(void); + void Event_NewPod(hhPod *pod); + void Event_LaunchPod(void); + void Event_SpawnBlastDebris(void); + void Event_ChargeEnemy(void); + void Event_GrabEnemy(void); + void Event_BiteEnemy(void); + void Event_DirectMoveToPosition(const idVec3 &pos); + void Event_BindUnfroze(idEntity *unfrozenBind); + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ); + void Event_GrabCheck(void); + void Event_MoveToGrabPosition(void); + void Event_CheckRange(void); + void Event_EnemyRangeZ(void); + + bool GrabEnemy(void); + + virtual void LinkScriptVariables(void); + + virtual void Ticker(void); + virtual idProjectile *LaunchProjectileAtVec(const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef); + virtual idProjectile *LaunchProjectileAtVec(const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef); + virtual void Killed(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); + virtual void SetEnemy(idActor *newEnemy); + + idVec3 podOffset; + float podRange; + int dripCount; + + idList< idEntityPtr > podList; + + idScriptInt AI_PODCOUNT; + idScriptBool AI_CHARGEDONE; + idScriptBool AI_SWOOP; + idScriptInt AI_DODGEDAMAGE; + + int nextWoundTime; + + idEntityPtr bindController; // Bind controller for tractor beam +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif + diff --git a/neo/prey/Prey/ai_harvester_simple.cpp b/neo/prey/Prey/ai_harvester_simple.cpp new file mode 100644 index 00000000..f132ad03 --- /dev/null +++ b/neo/prey/Prey/ai_harvester_simple.cpp @@ -0,0 +1,838 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_AntiProjectileAttack("", "e"); +const idEventDef MA_StartPreDeath("" ); +const idEventDef MA_HandlePassageway("", NULL); +const idEventDef MA_EnterPassageway("", "e",NULL); +const idEventDef MA_ExitPassageway("", "e",NULL); +const idEventDef MA_UseThisPassageway("useThisPassageway", "ed",NULL); +const idEventDef MA_GibOnDeath("gibOnDeath", "d"); + +CLASS_DECLARATION( hhMonsterAI, hhHarvesterSimple ) + EVENT( MA_OnProjectileLaunch, hhHarvesterSimple::Event_OnProjectileLaunch ) + EVENT( MA_AntiProjectileAttack, hhHarvesterSimple::Event_AntiProjectileAttack ) + EVENT( MA_StartPreDeath, hhHarvesterSimple::Event_StartPreDeath ) + EVENT( MA_HandlePassageway, hhHarvesterSimple::Event_HandlePassageway ) + EVENT( MA_EnterPassageway, hhHarvesterSimple::Event_EnterPassageway ) + EVENT( MA_ExitPassageway, hhHarvesterSimple::Event_ExitPassageway ) + EVENT( MA_UseThisPassageway, hhHarvesterSimple::Event_UseThisPassageway ) + EVENT( EV_Broadcast_AppendFxToList, hhHarvesterSimple::Event_AppendFxToList ) + EVENT( MA_GibOnDeath, hhHarvesterSimple::Event_GibOnDeath ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// +// Spawn() +// +void hhHarvesterSimple::Spawn(void) { + allowPreDeath = true; + lastAntiProjectileAttack = 0; + lastPassagewayTime = 0; + passageCount = spawnArgs.GetInt("passage_count", "0"); // Used for passing to and from torso + bSmokes = spawnArgs.GetBool("smokes", "0"); + bGibOnDeath = false; +} + +// +// Event_OnProjectileLaunch() +// +void hhHarvesterSimple::Event_OnProjectileLaunch(hhProjectile *proj) { + + // Can't launch again yet + float min = spawnArgs.GetFloat( "dda_delay_min" ); + float max = spawnArgs.GetFloat( "dda_delay_max" ); + float delay = min + (max - min) * (1.0f - gameLocal.GetDDAValue()); + + if(gameLocal.GetTime() - lastAntiProjectileAttack < delay) + return; + + // The person who launched this projectile wasn't someone to worry about + if(proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE))) + return; + + // TODO: more intelligent checks for if we should launch the anti-projectile chaff + idVec3 fw = viewAxis[0]; + idVec3 projFw = proj->GetAxis()[0]; + if(proj->GetOwner()) + projFw = proj->GetOwner()->GetAxis()[0]; + float dot = fw * projFw; + if(dot > -.7f) + return; + + ProcessEvent(&MA_AntiProjectileAttack, proj); +} + + +// +// Event_AntiProjectileAttack() +// +void hhHarvesterSimple::Event_AntiProjectileAttack(hhProjectile *proj) { + + if(!spawnArgs.GetBool("block_projectiles", "1")) { + return; + } + + const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chaff") ); + if ( !projectileDef ) { + gameLocal.Error( "Unknown def_chaff: %s\n", spawnArgs.GetString("def_chaff") ); + } + hhProjectile* projectile = NULL; + + //bjk: new parms + int numProj = spawnArgs.GetInt("shieldNumProj", "10"); + float spread = DEG2RAD( spawnArgs.GetFloat("shieldSpread", "10") ); + float yaw = DEG2RAD( spawnArgs.GetFloat("shieldYawSpread", "10") ); + + //StartSound("snd_fire_chaff", SND_CHANNEL_ANY, 0, true, NULL); + torsoAnim.PlayAnim( GetAnimator()->GetAnim( "antiprojectile_attack" ) ); + + idVec3 dir = proj->GetOrigin() - GetOrigin(); + dir.Normalize(); + dir = dir+idVec3(0.f, 0.f, -.3f); // bjk: bias towards ground + dir.Normalize(); + idMat3 muzzleAxis = dir.ToMat3(); + // todo: read this in from a bone + idVec3 launchPos = GetOrigin() + idVec3(0.0f, 0.0f, 64.0f); + + for(int i=0; iCreate(this, launchPos, dir); + projectile->Launch(launchPos, dir, idVec3(0.0f, 0.0f, 0.0f)); + } + + lastAntiProjectileAttack = gameLocal.GetTime(); +} + +bool hhHarvesterSimple::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + int preDeathThresh = spawnArgs.GetInt("predeath_thresh", "-1"); + if(allowPreDeath && !bGibOnDeath && preDeathThresh > 0 && health < preDeathThresh) { + allowPreDeath = false; // We've tried, cannot try anymore + float preDeathFreq = spawnArgs.GetFloat("predeath_freq", "1"); + if(gameLocal.random.RandomFloat() < preDeathFreq) { + ProcessEvent(&MA_StartPreDeath); + } + } + + if ( bSmokes ) { + if ( !fxSmoke[0].IsValid() && health <= AI_PASSAGEWAY_HEALTH ) { + SpawnSmoke(); + } else if (fxSmoke[0].IsValid() && health > AI_PASSAGEWAY_HEALTH) { + ClearSmoke(); + } + } + + return( idAI::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhHarvesterSimple::Event_StartPreDeath(void) { + int i; + + // No torso? Just kill myself ..... + if(!spawnArgs.GetString("def_torso")) { + ProcessEvent(&AI_Kill); + return; + } + + // Spawn death fx, if any + const char *fx = spawnArgs.GetString("fx_death"); + if (fx && fx[0]) { + hhFxInfo fxInfo; + + fxInfo.RemoveWhenDone(true); + BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo); + } + + // do blood splats + float size = spawnArgs.GetFloat("decal_torso_pop_size","96"); + + // copy my target keys to the keys that will spawn the torso + idDict dict; + const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_torso")); + if ( !torsoDict ) { + gameLocal.Error("Unknown def_torso: %s\n", spawnArgs.GetString("def_torso")); + } + dict.Copy(*torsoDict); + for(i=0;i 0) { + sprintf(key, "target%i", i); + } + + dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name); + } + + // Passageway variables + dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH); + dict.SetInt("passage_count", passageCount); + dict.SetVector("origin", GetOrigin()); + dict.SetMatrix("rotation", GetAxis()); + + // remove my target keys (so they don't get fired) + targets.Clear(); + + // Spawn the torso + idEntity *e; + hhMonsterAI *aiTorso; + if(!gameLocal.SpawnEntityDef(dict, &e)) + gameLocal.Error("Failed to spawn ai torso."); + HH_ASSERT(e && e->IsType(hhMonsterAI::Type)); + aiTorso = static_cast(e); + HH_ASSERT(aiTorso != NULL); + + // Throw gibs + idStr debrisDef = spawnArgs.GetString("def_gibDebrisSpawnerPreDeath"); + if(debrisDef.Length() > 0) { + hhUtils::SpawnDebrisMass(debrisDef.c_str(), this, 1.0f); + } + + health = 0; + + // remove myself + Hide(); + PostEventMS(&EV_Remove, 3000); +} + +void hhHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + return; + } + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( !AI_DEAD ) { + if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { + spawnArgs.Set( "bAlwaysGib", "0" ); + allowPreDeath = false; + } else { + allowPreDeath = true; + } + if ( damageDef && damageDef->GetBool( "no_special_death" ) ) { + allowPreDeath = false; + } + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +int hhHarvesterSimple::EvaluateReaction( const hhReaction *react ) { + // Let the script limit the distance of Climb reactions we consider + if ( react->desc->effect == hhReactionDesc::Effect_Climb && AI_CLIMB_RANGE >= 0.0f && ( react->causeEntity->GetOrigin() - GetOrigin()).LengthSqr() > AI_CLIMB_RANGE ) { + return 0; + } + + // If we're supposed to use a specific passageway, only accept it and reject the others + if ( nextPassageway.IsValid() && react->desc->effect == hhReactionDesc::Effect_Passageway ) { + if (react->causeEntity.GetEntity() == nextPassageway.GetEntity()) { + return 100; + } else { + return 0; + } + } + + int rank = hhMonsterAI::EvaluateReaction( react ); + if ( react->desc->effect == hhReactionDesc::Effect_Passageway && + react->causeEntity.GetEntity() == lastPassageway.GetEntity() ) { + if ( lastPassagewayTime + 10000 > gameLocal.time ) { + if (rank > 10) { + // Discourage entering the same passageway we just left + return 10; + } + } else { + // Ignore previous passageway for 10 seconds + return 0; + } + } + return rank; +} + +#define CheckPassageFreqInitial 6000 +void hhHarvesterSimple::Event_EnterPassageway(hhAIPassageway *pn) { + CancelEvents( &MA_OnProjectileLaunch ); // Clear any anti-projectile attacks + CancelEvents( &MA_AntiProjectileAttack ); // Clear any anti-projectile attacks + if (nextPassageway.IsValid()) { + if (nextPassageway.GetEntity() != pn) { + gameLocal.Warning("hhAIPassageway entered was not the specified one!\n"); + } + nextPassageway.Clear(); + } + + if(currPassageway != NULL) { + gameLocal.Error("%s tried to ENTER a passageway but already had a passageway!", (const char*)name); + } + + HH_ASSERT(currPassageway == NULL); + Hide(); // Should already be hidden but just to make sure + + // Only consider passageCount if damaged (ignores scripted passage sequences) or if it's the torso + if (health < spawnHealth || !idStr::Icmp(spawnArgs.GetString("classname"), "monster_harvester_torso") ) { + passageCount++; + if (passageCount > 1) { + //TODO make this affected by the DDA system + AI_PASSAGEWAY_HEALTH = AI_PASSAGEWAY_HEALTH * 0.75f; // Reduce passageway seeking health level to 3/4 of it's original value + } + } + + GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0); + Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Idle", (int)0); + Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Idle", (int)0); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); + + StartSound("snd_inside_passage", SND_CHANNEL_VOICE, SSF_LOOPING, true, NULL); + + currPassageway = pn; + if(GetPhysics()) + GetPhysics()->SetContents(0); + + // Does this passage way give us health? + int h = 0; + pn->spawnArgs.GetInt("give_health", "0", h); + health += h; + health = idMath::ClampInt(0, spawnArgs.GetInt("health"), health); + + + // Should we transform to another monster when entering? + idStr transTo; + if(spawnArgs.GetString("passage_transform_to", "", transTo)) { + const idDict *def = gameLocal.FindEntityDefDict( transTo ); + if(!def) { + gameLocal.Error("Unknown def : %s", (const char*)transTo); + } + + // copy my target keys to the keys of the transformed-to entity + idDict dict; + dict.Copy(*def); + for(int i=0;i 0) + sprintf(key, "target%i", i); + + dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name); + } + + // Passageway variables + dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH); + dict.SetInt("passage_count", passageCount); + + idEntity *e = NULL; + gameLocal.SpawnEntityDef(dict, &e); + HH_ASSERT( e->IsType( hhHarvesterSimple::Type ) ); + hhHarvesterSimple *ai = static_cast(e); + ai->SetOrigin(GetOrigin()); + ai->SetAxis(GetAxis()); + ai->Event_EnterPassageway(pn); + targets.Clear(); + PostEventSec(&EV_Remove, 0.1f); + return; + } + + + PostEventMS(&MA_HandlePassageway, CheckPassageFreqInitial); +} + +void hhHarvesterSimple::Event_ExitPassageway(hhAIPassageway *pn) { + if(currPassageway == NULL) { + gameLocal.Error("%s tried to EXIT a passageway but did NOT have a curr passageway!", (const char*)name); + } + + HH_ASSERT(currPassageway != NULL); + + // Store this passageway so we don't jump right back into it. + lastPassageway = idEntityPtr (pn); + lastPassagewayTime = gameLocal.time; + + if(GetPhysics()) + GetPhysics()->SetContents(CONTENTS_BODY); + currPassageway = NULL; + AI_ACTIVATED = true; // Sometimes this is NOT true?!?! + StopSound(SND_CHANNEL_VOICE, true); + + // Teleport to new pos + idAngles angs = pn->GetAxis().ToAngles(); + idVec3 pos = pn->GetExitPos(); + Teleport( pos, angs, pn ); + Show(); + + current_yaw = angs.yaw; + ideal_yaw = angs.yaw; + turnVel = 0.0f; + HH_ASSERT(FacingIdeal()); + + // Exit anim to play? + idStr exitAnim = pn->spawnArgs.GetString("exit_anim"); + if(exitAnim.Length() && GetAnimator()->HasAnim(exitAnim)) { + GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); + torsoAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) ); + legsAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) ); + } +} + +// +// Event_HandlePassageway() +// +#define MaxExits 32 +#define CheckPassageFreq 3000 +void hhHarvesterSimple::Event_HandlePassageway(void) { + + if(currPassageway == NULL) { + gameLocal.Error("%s tried to handle a passageway but did NOT have a passageway!", (const char*)name); + } + + HH_ASSERT(currPassageway != NULL); + hhAIPassageway *exitNode = NULL; + + // CATCH 'went inside hole - never came out' bug + if(currPassageway.GetEntity()->lastEntered == this && gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > 30000) { + gameLocal.Warning("%s has been stuck in a passageway for >30 secs, killing it off.", (const char*)name); + fl.takedamage = true; + // Use hhMonsterAI::Damage because our Damage returns if we're the attacker + hhMonsterAI::Damage(this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT); + return; + } + + // No other way out - gotta go out the way we came in + if(currPassageway->targets.Num() <= 0) { + exitNode = currPassageway.GetEntity(); + } + // We can go out another way + else { + hhAIPassageway *possibleExits[MaxExits]; + hhAIPassageway *tmp; + int count = 0; + + bool isRandom; + if (gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > SEC2MS(10)) { + // Try to prevent us from being stuck after 10 seconds + isRandom = true; + } else { + // Choose possibleExits[] randomly if we have no enemy or based on chance. Otherwise choose based on line-of-sight to the player + isRandom = !enemy.IsValid() || gameLocal.random.RandomInt(100) > 50; + } + trace_t tr; + idVec3 toPos; + if (!isRandom) { + toPos = enemy->GetOrigin(); + } + + // We've tried once, now we can use our entrance as an exit + if(gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > CheckPassageFreqInitial + 1000) { + count = 1; + possibleExits[0] = currPassageway.GetEntity(); // We can always go out the same way we came in + } + for(int i=0;itargets.Num();i++) { + if(currPassageway->targets[i] != NULL && currPassageway->targets[i].GetEntity()->IsType(hhAIPassageway::Type)) { + if (isRandom) { + possibleExits[count++] = static_cast(currPassageway->targets[i].GetEntity()); + } else { + tmp = static_cast(currPassageway->targets[i].GetEntity()); + // Choose based on visibility to our enemy + gameLocal.clip.TracePoint( tr, tmp->GetOrigin(), toPos, MASK_SHOT_BOUNDINGBOX, tmp ); + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemy.GetEntity() ) ) { + lastVisibleEnemyPos = toPos; // Can now see enemy + possibleExits[count++] = tmp; + } + } + } + + // We've got enough + if(count >= MaxExits) + break; + } + + if(count == 0) { + exitNode = currPassageway.GetEntity(); + } else if ( enemy.IsValid() && gameLocal.random.RandomInt(100) > 70 ) { + // Find the one closest to the last known enemy pos + if (!exitNode) { + float bestDistance = -1.0f, distSq; + for (int i = 0; i < count; i++) { + distSq = ( possibleExits[i]->GetOrigin() - lastVisibleEnemyPos ).LengthSqr(); + if ( bestDistance == -1.0f || distSq < bestDistance ) { + exitNode = possibleExits[i]; + bestDistance = distSq; + } + } + } + } else { + // Pick a random one + int randExit = gameLocal.random.RandomInt(count); // TODO: Randomness isn't very random! Fix! + HH_ASSERT(randExit >= 0 && randExit < count); + exitNode = possibleExits[randExit]; + } + } + + if(!exitNode) + exitNode = currPassageway.GetEntity(); + + // The one we picked is disabled, so go out where we came in + // TODO: make this time based, try a few more times before going back out + if(!exitNode->IsPassagewayEnabled()) { + exitNode = currPassageway.GetEntity(); + } + + // Special case: The passageway is never meant to be exited from, use it's specified exit point instead + const char *altPassage = exitNode->spawnArgs.GetString("force_passageway", ""); + if (altPassage && altPassage[0]) { + idEntity *node = gameLocal.FindEntity(altPassage); + if (node && node->IsType(hhAIPassageway::Type)) { + exitNode = reinterpret_cast (node); + } + } + + // Make sure no one is blocking where we want to go out + idBounds myBnds = GetPhysics()->GetBounds(); + myBnds.Expand(12.0f); + myBnds.TranslateSelf(exitNode->GetExitPos()); + idEntity *ents[MAX_GENTITIES]; + int count = gameLocal.clip.EntitiesTouchingBounds(myBnds, -1, ents, MAX_GENTITIES); + for(int i=0;iIsHidden() && (ents[i]->IsType(idActor::Type) || ents[i]->IsType(hhMoveable::Type))) { + // Chunk moveables + if ( ents[i]->IsType(idMoveable::Type) ) { + ents[i]->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT ); + ents[i]->SquishedByDoor(this); + continue; + } + gameLocal.Warning("Passageway exit blocked.\n"); + PostEventMS(&MA_HandlePassageway, CheckPassageFreq); + if(ai_debugBrain.GetInteger() != 0) { + gameRenderWorld->DebugBounds(colorYellow, myBnds, vec3_origin, 2000); + } + return; + } + } + + // Else we can just exit now - its safe + exitNode->ProcessEvent(&EV_Activate, this); +} + +#define LinkScriptVariable( name ) name.LinkTo(scriptObject, #name) +void hhHarvesterSimple::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable(AI_CLIMB_RANGE); + LinkScriptVariable(AI_PASSAGEWAY_HEALTH); +} + +void hhHarvesterSimple::Event_UseThisPassageway(hhAIPassageway *pn, bool force) { + nextPassageway.Assign(pn); + + if (force) { // Snap to position + Event_FindReaction("passageway"); + hhReaction *reaction = targetReaction.GetReaction(); + if (!reaction) { + gameLocal.Warning("useThisPassageway could not find a valid reaction!\n"); + } else { + idVec3 tp = GetTouchPos( reaction->causeEntity.GetEntity(), reaction->desc ); + SetOrigin(tp); + } + } +} + +/* +===================== +hhHarvesterSimple::Save +===================== +*/ +void hhHarvesterSimple::Save( idSaveGame *savefile ) const { + savefile->WriteBool( allowPreDeath ); + savefile->WriteInt( lastAntiProjectileAttack ); + savefile->WriteInt( passageCount ); + savefile->WriteBool( bGibOnDeath ); + savefile->WriteInt( lastPassagewayTime ); + + lastPassageway.Save( savefile ); + nextPassageway.Save( savefile ); +} + +/* +===================== +hhHarvesterSimple::Restore +===================== +*/ +void hhHarvesterSimple::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( allowPreDeath ); + savefile->ReadInt( lastAntiProjectileAttack ); + savefile->ReadInt( passageCount ); + savefile->ReadBool( bGibOnDeath ); + savefile->ReadInt( lastPassagewayTime ); + + lastPassageway.Restore( savefile ); + nextPassageway.Restore( savefile ); + + bSmokes = spawnArgs.GetBool("smokes", "0"); +} + +void hhHarvesterSimple::SpawnSmoke(void) { + const char *bones[MAX_HARVESTER_LEGS]; + bones[0] = spawnArgs.GetString( "bone_smoke_front_left" ); + bones[1] = spawnArgs.GetString( "bone_smoke_front_right" ); + bones[2] = spawnArgs.GetString( "bone_smoke_rear_left" ); + bones[3] = spawnArgs.GetString( "bone_smoke_rear_right" ); + + const char *psystem = spawnArgs.GetString( "fx_smoke" ); + if ( psystem && *psystem ) { + idVec3 bonePos; + idMat3 boneAxis; + + for(int i = 0; i < MAX_HARVESTER_LEGS; i++) { + if( fxSmoke[i] != NULL ) { + gameLocal.Warning("Harvester already had smoke particles for leg %i.\n", i); + continue; + } + + hhFxInfo fxInfo; + + this->GetJointWorldTransform( bones[i], bonePos, boneAxis ); + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( bones[i] ); + fxInfo.Toggle(); + fxInfo.RemoveWhenDone( false ); + + BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList ); + } + } +} + +void hhHarvesterSimple::ClearSmoke(void) { + for(int i=0; i < MAX_HARVESTER_LEGS; i++) { + if(fxSmoke[i].IsValid()) { + fxSmoke[i]->Nozzle(false); + } + } + + for(int i = 0; i < MAX_HARVESTER_LEGS; i++) { + SAFE_REMOVE(fxSmoke[MAX_HARVESTER_LEGS]); + } +} + +void hhHarvesterSimple::Event_AppendFxToList(hhEntityFx *fx) { + for(int i = 0; i < MAX_HARVESTER_LEGS; ++i) { + if(fxSmoke[i] != NULL) { + continue; + } + + fxSmoke[i] = fx; + return; + } +} + +void hhHarvesterSimple::Event_GibOnDeath(const idList* parmList) { + bGibOnDeath = (atoi((*parmList)[0].c_str()) != 0); +} + +void hhHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + ClearSmoke(); + hhMonsterAI::Killed(inflictor, attacker, damage, dir, location); + + if (bGibOnDeath) { + Gib(vec3_origin, "damage_gib"); + } +} + +void hhHarvesterSimple::Event_Activate(idEntity *activator) { + if (fl.hidden) { + // If we're hidden, we're probably birthing out of passageway. Figure out which one. + hhAIPassageway *nearest = NULL; + float dist, nearDist = idMath::INFINITY; + hhAIPassageway *passage = reinterpret_cast (gameLocal.FindEntityOfType( hhAIPassageway::Type, NULL )); + while (passage) { + dist = (passage->GetOrigin() - GetOrigin()).Length(); + if (dist < nearDist) { + nearDist = dist; + nearest = passage; + } + passage = reinterpret_cast (gameLocal.FindEntityOfType( hhAIPassageway::Type, passage )); + } + + // If we found one near us and it's near enough, mark it as our last passageway + if (nearest && nearDist < 250.0f) { + lastPassageway = nearest; + lastPassagewayTime = gameLocal.time; + } + } + hhMonsterAI::Event_Activate(activator); +} + +void hhHarvesterSimple::AnimMove( void ) { + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + // Turn on physics to prevent flying harvesters + BecomeActive(TH_PHYSICS); + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + +#ifdef HUMANHEAD //shrink functionality + float scale = renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM1]; + if ( scale > 0.0f && scale < 2.0f ) { + delta *= scale; + } +#endif + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + if ( ai_debugMove.GetBool() ) { + // HUMANHEAD JRM - so we can see if grav is on or off + if(disableGravity) { + gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 ); + } else { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( hhHarvesterSimple::Type ) ) { + StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER ); + return; + } + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhHarvesterSimple::Event_CanBecomeSolid( void ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + + num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( hit == this ) { + continue; + } + + // Special case, crush any moveables blocking our start point + if ( hit->IsType( idMoveable::Type ) ) { + hit->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT ); + hit->SquishedByDoor(this); + } + + if ( !hit->fl.takedamage ) { + continue; + } + + if ( physicsObj.ClipContents( cm ) ) { + idThread::ReturnFloat( false ); + return; + } + } + + idThread::ReturnFloat( true ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_harvester_simple.h b/neo/prey/Prey/ai_harvester_simple.h new file mode 100644 index 00000000..e5da0879 --- /dev/null +++ b/neo/prey/Prey/ai_harvester_simple.h @@ -0,0 +1,79 @@ + +#ifndef __PREY_AI_HARVESTERSIMPLE_H__ +#define __PREY_AI_HARVESTERSIMPLE_H__ + +extern const idEventDef AI_OnProjectileLaunch; +extern const idEventDef MA_EnterPassageway; +extern const idEventDef MA_ExitPassageway; + +#define MAX_HARVESTER_LEGS 4 + +class hhHarvesterSimple : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhHarvesterSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + virtual void Event_StartPreDeath(void) {}; + void Event_OnProjectileLaunch(hhProjectile *proj) {}; + void Event_AntiProjectileAttack(hhProjectile *proj) {}; + void Event_EnterPassageway(hhAIPassageway *pn) {}; + void Event_ExitPassageway(hhAIPassageway *pn) {}; + void Event_HandlePassageway(void) {}; + void Event_UseThisPassageway(hhAIPassageway *pn, bool force) {}; + void Event_AppendFxToList(hhEntityFx *fx) {}; + void Event_GibOnDeath(const idList* parmList) {}; + virtual void Event_Activate(idEntity *activator) {}; + virtual void Event_CanBecomeSolid(void) {}; +#else + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void LinkScriptVariables(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + +protected: + + void Spawn(void); + + virtual int EvaluateReaction( const hhReaction *react ); + + void SpawnSmoke(void); + void ClearSmoke(void); + + //predeath torso code + virtual void Event_StartPreDeath(void); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + bool allowPreDeath; // TRUE if we will allow predeath + + virtual void AnimMove(void); + + //anti-projectile chaff + void Event_OnProjectileLaunch(hhProjectile *proj); + void Event_AntiProjectileAttack(hhProjectile *proj); + void Event_EnterPassageway(hhAIPassageway *pn); + void Event_ExitPassageway(hhAIPassageway *pn); + void Event_HandlePassageway(void); + void Event_UseThisPassageway(hhAIPassageway *pn, bool force); + void Event_AppendFxToList(hhEntityFx *fx); + void Event_GibOnDeath(const idList* parmList); + virtual void Event_Activate(idEntity *activator); + virtual void Event_CanBecomeSolid(void); + + int lastAntiProjectileAttack; + int lastPassagewayTime; + idEntityPtr lastPassageway; + idEntityPtr nextPassageway; + idScriptFloat AI_CLIMB_RANGE; + idScriptFloat AI_PASSAGEWAY_HEALTH; + + idEntityPtr fxSmoke[MAX_HARVESTER_LEGS]; + + int passageCount; + bool bSmokes; + bool bGibOnDeath; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_hunter_simple.cpp b/neo/prey/Prey/ai_hunter_simple.cpp new file mode 100644 index 00000000..0cdbebbb --- /dev/null +++ b/neo/prey/Prey/ai_hunter_simple.cpp @@ -0,0 +1,2141 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_LaserOn("laserOn", NULL); +const idEventDef MA_LaserOff("laserOff", NULL); +const idEventDef MA_EscapePortal( "escapePortal" ); +const idEventDef MA_AssignSniperFx( "", "e" ); +const idEventDef MA_KickAngle( "kickAngle", "v" ); +const idEventDef MA_AssignFlashLightFx( "", "e" ); +const idEventDef MA_FlashLightOn( "flashLightOn" ); +const idEventDef MA_FlashLightOff( "flashLightOff" ); +const idEventDef MA_EnemyCanSee( "enemyCanSee", NULL, 'd' ); +const idEventDef MA_PrintAction( "printAction", "s" ); +const idEventDef MA_GetAlly( "getAlly", NULL, 'e' ); +const idEventDef MA_TriggerDelay( "triggerDelay", "Ef" ); +const idEventDef MA_CallBackup( "callBackup", "f" ); +const idEventDef MA_SaySound( "saySound", "s", 'f' ); +const idEventDef MA_CheckRush( "" ); +const idEventDef MA_CheckRetreat( "" ); +const idEventDef MA_SayEnemyInfo( "sayEnemyInfo", NULL, 'd' ); +const idEventDef MA_SetNextVoiceTime( "setNextVoiceTime", "d" ); +const idEventDef MA_GetCoverNode( "getCoverNode", NULL, 'v' ); +const idEventDef MA_GetCoverPoint( "getCoverPoint", NULL, 'v' ); +const idEventDef MA_OnProjectileHit( "", "e" ); +const idEventDef MA_GetSightNode( "getSightNode", NULL, 'v' ); +const idEventDef MA_GetNearSightPoint( "getNearSightPoint", NULL, 'v' ); +const idEventDef MA_Blocked( "" ); +const idEventDef MA_EnemyPortal( "", "e" ); +const idEventDef MA_GetEnemyPortal( "getEnemyPortal", NULL, 'e' ); +const idEventDef MA_EnemyVehicleDocked( "enemyVehicleDocked", NULL, 'd' ); + +CLASS_DECLARATION(hhMonsterAI, hhHunterSimple) + EVENT( MA_OnProjectileLaunch, hhHunterSimple::Event_OnProjectileLaunch ) + EVENT( MA_OnProjectileHit, hhHunterSimple::Event_OnProjectileHit ) + EVENT( MA_LaserOn, hhHunterSimple::Event_LaserOn) + EVENT( MA_LaserOff, hhHunterSimple::Event_LaserOff) + EVENT( MA_EscapePortal, hhHunterSimple::Event_EscapePortal ) + EVENT( MA_AssignSniperFx, hhHunterSimple::Event_AssignSniperFx ) + EVENT( MA_KickAngle, hhHunterSimple::Event_KickAngle ) + EVENT( MA_AssignFlashLightFx, hhHunterSimple::Event_AssignFlashLightFx ) + EVENT( MA_FlashLightOn, hhHunterSimple::Event_FlashLightOn ) + EVENT( MA_FlashLightOff, hhHunterSimple::Event_FlashLightOff ) + EVENT( MA_EnemyCanSee, hhHunterSimple::Event_EnemyCanSee ) + EVENT( MA_PrintAction, hhHunterSimple::Event_PrintAction ) + EVENT( MA_GetAlly, hhHunterSimple::Event_GetAlly ) + EVENT( MA_TriggerDelay, hhHunterSimple::Event_TriggerDelay ) + EVENT( MA_CallBackup, hhHunterSimple::Event_CallBackup ) + EVENT( MA_SaySound, hhHunterSimple::Event_SaySound ) + EVENT( MA_CheckRush, hhHunterSimple::Event_CheckRush ) + EVENT( MA_CheckRetreat, hhHunterSimple::Event_CheckRetreat ) + EVENT( MA_SayEnemyInfo, hhHunterSimple::Event_SayEnemyInfo ) + EVENT( MA_SetNextVoiceTime, hhHunterSimple::Event_SetNextVoiceTime ) + EVENT( MA_GetCoverNode, hhHunterSimple::Event_GetCoverNode ) + EVENT( MA_GetCoverPoint, hhHunterSimple::Event_GetCoverPoint ) + EVENT( MA_GetSightNode, hhHunterSimple::Event_GetSightNode ) + EVENT( MA_GetNearSightPoint, hhHunterSimple::Event_GetNearSightPoint ) + EVENT( MA_Blocked, hhHunterSimple::Event_Blocked ) + EVENT( MA_EnemyPortal, hhHunterSimple::Event_EnemyPortal ) + EVENT( MA_GetEnemyPortal, hhHunterSimple::Event_GetEnemyPortal ) + EVENT( MA_EnemyVehicleDocked, hhHunterSimple::Event_EnemyVehicleDocked ) +END_CLASS + +hhHunterSimple::hhHunterSimple() { + beamLaser = 0; + kickSpeed = ang_zero; + kickAngles = ang_zero; + alternateAccuracy = false; + bFlashLight = false; + nodeList.Clear(); + ally.Clear(); + nextEnemyCheck = 0; + enemyPortal = 0; + nextBlockCheckTime = 0; + lastMoveOrigin = vec3_zero; + nextSpiritCheck = 0; //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes +} + +void hhHunterSimple::Spawn() { + bFlashLight = false; + lastChargeTime = 0; + endSpeechTime = 0; + enemyRushCount = 0; + enemyRetreatCount = 0; + nextVoiceTime = 0; + currentAction.Clear(); + currentSpeech.Clear(); + flashlightLength = spawnArgs.GetFloat( "flashlightLength", "90" ); + flashlightTime = gameLocal.time + SEC2MS( spawnArgs.GetInt( "flashlight_delay" ) ); + beamLaser = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) ); + initialOrigin = GetOrigin(); + if( beamLaser.IsValid() ) { + beamLaser->Activate( false ); + idMat3 boneAxis; + idVec3 boneOrigin; + GetJointWorldTransform( spawnArgs.GetString("laser_bone"), boneOrigin, boneAxis ); + beamLaser->SetOrigin( boneOrigin ); + beamLaser->BindToJoint( this, spawnArgs.GetString("laser_bone"), false ); + } + PostEventSec( &MA_CheckRush, 0 ); + PostEventSec( &MA_CheckRetreat, 0 ); +} + +void hhHunterSimple::Event_PostSpawn() { + hhMonsterAI::Event_PostSpawn(); + + //look for ainodes in targets list + for ( int i=0;iIsType( hhAINode::Type ) ) { + nodeList.Append( targets[i] ); + } + } + if ( nodeList.Num() <= 0 ) { + //if targets list had none, find dynamically + idEntity *ent; + float distSq; + aasPath_t path; + int toAreaNum, areaNum; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->spawnArgs.GetBool( "ainode", "0" ) ) { + continue; + } + distSq = (GetOrigin() - ent->GetOrigin()).LengthSqr(); + if ( distSq > 2250000 ) { //less than 1500 + continue; + } + //check reachability + toAreaNum = PointReachableAreaNum( ent->GetOrigin() ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, ent->GetOrigin() ) ) { + continue; + } + nodeList.Append( ent ); + } + } +} + +void hhHunterSimple::Event_OnProjectileLaunch(hhProjectile *proj) { + if ( !enemy.IsValid() || !proj || AI_VEHICLE ) { + return; + } + if ( GetPhysics() && GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 ) ) { + return; + } + + float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" ); + float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" ); + float dodgeChance = 0.6f; + + dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() ); + + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance ); + } + if ( proj->GetOwner() && proj->GetOwner() == enemy.GetEntity() ) { + float reactChance = proj->spawnArgs.GetFloat( "react_chance" ); + if ( gameLocal.random.RandomFloat() < reactChance ) { + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "say %s\n", proj->spawnArgs.GetString( "react_sound" ) ); + } + AI_ENEMY_RETREAT = true; + PostEventSec( &MA_SaySound, gameLocal.random.RandomFloat(), proj->spawnArgs.GetString( "react_sound" ) ); + } + } + if ( gameLocal.random.RandomFloat() > dodgeChance ) { + return; + } + if ( proj->GetOwner() && proj->GetOwner()->IsType( hhHunterSimple::Type ) ) { + return; + } + + //determine which side to dodge to + const function_t *newstate = NULL; + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + idVec3 povLeft, povUp; + povToTarget.OrthogonalBasis(povLeft, povUp); + povLeft.Normalize(); + + idVec3 projVel = proj->GetPhysics()->GetLinearVelocity(); + projVel.Normalize(); + float dot = povLeft * projVel; + + if ( dot < 0 ) { + newstate = GetScriptFunction( "state_DodgeRight" ); + } else { + newstate = GetScriptFunction( "state_DodgeLeft" ); + } + + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhHunterSimple::Event_LaserOn() { + if ( beamLaser.IsValid() ) { + beamLaser->Activate( true ); + } +} + +void hhHunterSimple::Event_LaserOff() { + if ( beamLaser.IsValid() ) { + beamLaser->Activate( false ); + } +} + +void hhHunterSimple::Event_KickAngle( const idAngles &ang ) { + kickSpeed = ang; +} + +void hhHunterSimple::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + + trace_t trace; + if ( flashlightTime <= 0 || gameLocal.time > flashlightTime ) { + flashlightTime = 0; + if ( !AI_DEAD && !IsHidden() && !InVehicle() ) { + idMat3 boneAxis; + idVec3 boneOrigin; + GetJointWorldTransform( "fx_barrel", boneOrigin, boneAxis ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, boneOrigin, boneOrigin + boneAxis[0] * flashlightLength, 10, 1 ); + } + gameLocal.clip.TracePoint( trace, boneOrigin, boneOrigin + boneAxis[0] * flashlightLength, MASK_OPAQUE, this ); + if ( trace.fraction < 1.0f ) { + Event_FlashLightOff(); + } else { + Event_FlashLightOn(); + } + } else if ( bFlashLight ) { + Event_FlashLightOff(); + } + } + if ( enemy.IsValid() && beamLaser.IsValid() && beamLaser->IsActivated() ) { + memset(&trace, 0, sizeof(trace)); + + float frametime = (gameLocal.time - gameLocal.previousTime); + kickSpeed-=4*kickSpeed*frametime/1000; + kickSpeed-=30*kickAngles*frametime/1000; + kickAngles+=kickSpeed*frametime/1000; + + idAngles ang = (lastVisibleEnemyPos + enemy->EyeOffset() - idVec3( 0,0,10 ) - beamLaser->GetOrigin()).ToAngles(); + ang += kickAngles; + idVec3 forward, up, right; + ang.ToVectors( &forward, &right, &up ); + gameLocal.clip.TracePoint( trace, beamLaser->GetOrigin(), beamLaser->GetOrigin() + forward * 4000, MASK_SHOT_BOUNDINGBOX, this ); + if ( trace.fraction < 1.0f ) { + idEntity *hitEnt = gameLocal.GetTraceEntity( trace ); + if ( hitEnt && hitEnt->IsType( idPlayer::Type ) || + ( hitEnt && hitEnt->IsType( hhVehicle::Type ) ) ) { + //if we hit the player, set the endpoint a little farther back + //to prevent the laser from stopping at our camera viewpoint + idVec3 offset = (trace.endpos - beamLaser->GetOrigin()).ToNormal() * 100; + beamLaser->SetTargetLocation( trace.endpos + offset ); + } else { + beamLaser->SetTargetLocation( trace.endpos ); + } + } else { + beamLaser->SetTargetLocation( beamLaser->GetOrigin() + forward * 5000 ); + } + } + + if ( ai_debugBrain.GetBool() && ally.IsValid() && ally->GetHealth() > 0 ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), ally->GetOrigin(), 10, 1 ); + } + + if ( health > 0 && ai_debugActions.GetBool() && currentAction.Length() ) { + if ( AI_ENEMY_VISIBLE ) { + gameRenderWorld->DrawText("visible", GetEyePosition() + idVec3(0.0f, 0.0f, 30.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + if ( AI_ENEMY_SHOOTABLE ) { + gameRenderWorld->DrawText("shootable", GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + gameRenderWorld->DrawText(va( "%s", currentAction.c_str() ), GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + if ( health > 0 && ai_printSpeech.GetBool() && currentSpeech.Length() ) { + gameRenderWorld->DrawText(va( "%s", currentSpeech.c_str() ), GetEyePosition() + idVec3(0.0f, 0.0f, 10.0f), 0.4f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + if ( endSpeechTime > 0 && gameLocal.time > endSpeechTime ) { + currentSpeech.Clear(); + endSpeechTime = 0; + } + } + + if ( enemy.IsValid() ) { + //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + if ( nextSpiritCheck > 0 && gameLocal.time > nextSpiritCheck && enemy->IsType( hhSpiritProxy::Type ) ) { + nextSpiritCheck = 0; + SetEnemy( gameLocal.GetLocalPlayer() ); + } + if ( AI_ENEMY_VISIBLE ) { + AI_ENEMY_LAST_SEEN = MS2SEC( gameLocal.realClientTime ); + } + if ( enemy->GetHealth() < spawnArgs.GetInt( "health_warning", "25" ) ) { + AI_ENEMY_HEALTH_LOW = true; + } + } +} + +idProjectile *hhHunterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + //jsh overridden to allow per-projectile accuracy + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef || AI_DEAD ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + if ( projectileDef->GetBool( "snipe", "0" ) ) { + attack_accuracy = 0; + } else { + if ( projectileDef->GetFloat( "attack_accuracy" ) ) { + attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" ); + } else { + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + } + } + + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + if ( projectileDef->GetFloat( "projectile_spread" ) ) { + projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" ); + } else { + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + } + if ( projectileDef->GetFloat( "num_projectiles" ) ) { + num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" ); + } else { + num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" ); + } + + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // hunter inaccuracy is calculated instead of random spread based + float hitchance; + if ( !projectileDef->GetFloat( "hit_chance", "0.2", hitchance ) ) { + hitchance = spawnArgs.GetFloat( "hit_chance" ); + } + float hitAccuracy; + if ( !projectileDef->GetFloat( "hit_accuracy", "0", hitAccuracy ) ) { + hitAccuracy = spawnArgs.GetFloat( "hit_accuracy" ); + } + float missAccuracy; + if ( !projectileDef->GetFloat( "miss_accuracy", "0", missAccuracy ) ) { + missAccuracy = spawnArgs.GetFloat( "miss_accuracy" ); + } + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + alternateAccuracy = !alternateAccuracy; + if ( gameLocal.random.RandomFloat() < hitchance ) { + //rolled a hit so add very little inaccuracy + ang.pitch += idMath::Fabs( idMath::Sin16( t * 5.1 ) * hitAccuracy ); + ang.yaw += idMath::Sin16( t * 6.7 ) * hitAccuracy; + } else { + float rand = gameLocal.random.RandomFloat(); + if ( alternateAccuracy ) { + rand *= -1; + } + ang.pitch += rand * missAccuracy; + if ( !alternateAccuracy ) { + rand *= -1; + } + ang.yaw += rand * missAccuracy; + } + + //quick hack that should be fine for now + bool oriented = GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 ); + if ( !oriented && clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhHunterSimple::Event_EscapePortal() { + static const char * passPrefix = "portal_"; + const char * portalDef; + idEntity * portal; + idDict portalArgs; + idList xferKeys; + idList xferValues; + const idKeyValue * buddyKV; + + + // Find out which portal def to spawn - If none specified, then exit; + buddyKV = spawnArgs.FindKey( "portal_buddy" ); + if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) { + // Case of a valid portal_buddy key, make a real portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else { + portalDef = spawnArgs.GetString( "def_fakeportal" ); + } + + if ( !portalDef || !portalDef[0] ) { + return; + } + + // Set the origin of the portal to us. + //portalArgs.SetVector( "origin", GetOrigin() ); + idVec3 escapePortalOffset = spawnArgs.GetVector( "escapePortalOffset", "40 0 0" ); + portalArgs.SetVector( "origin", GetOrigin() + ( escapePortalOffset * GetAxis() ) ); + portalArgs.SetFloat( "angle", GetAxis()[0].ToYaw() - 180 ); + + // Set the portal draw distance to zero (so the player cannot see through the creature portal) + portalArgs.Set( "shaderParm5", "0" ); + + // Pass along all 'portal_' keys to the portal's spawnArgs; + hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues ); + for ( int i = 0; i < xferValues.Num(); ++i ) { + xferKeys[ i ].StripLeadingOnce( passPrefix ); + portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + } + + // Set the name of the associated game portal so it can be turned on and off + portalArgs.Set( "gamePortalName", GetName() ); + + // Spawn the portal + portal = gameLocal.SpawnObject( portalDef, &portalArgs ); + if ( !portal ) { + return; + } + + // Move the portal up some pre determinted amt, since its origin is in the middle of it + float offset = spawnArgs.GetFloat( "offset_portal", 0 ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) ); + + // Update the camera stuff + portal->ProcessEvent( &EV_UpdateCameraTarget ); + + // Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal + //? Should we always pass in the player? + portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() ); + + // Maybe wait for it to finish? + + +} + +void hhHunterSimple::Event_FindReaction( const char* effect ) { + idEntity* ent; + hhReaction* react; + hhReactionDesc::Effect react_effect; + idEntity* bestEnt = NULL; + int bestReactIndex = -1; + float bestDistance = -1; + float meToReact, enemyToReact; + int bestRank = -1; + + react_effect = hhReactionDesc::StrToEffect( effect ); + + if( react_effect == hhReactionDesc::Effect_Invalid ) { + gameLocal.Warning( "unknown effect '%s' requested from FindReaction", effect ); + idThread::ReturnEntity( NULL ); + return; + } + + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if( !ent || ent->fl.isDormant || !ent->fl.refreshReactions ) { + continue; + } + + for( int j = 0; j < ent->GetNumReactions(); j++ ) { + react = ent->GetReaction( j ); + if( !react || !react->IsActive() ) { + continue; + } + if( react_effect != react->desc->effect ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flag_Exclusive ) { //check exclusiveness + if ( react->exclusiveOwner.IsValid() && react->exclusiveOwner->health <= 0 ) { + react->exclusiveOwner.Clear(); + } + if( react->exclusiveOwner.GetEntity() && react->exclusiveOwner != this ) { + continue; + } + } + //Skip based on flag requirements + if( (react->desc->flags & hhReactionDesc::flagReq_RangeAttack) && !AI_HAS_RANGE_ATTACK ) { + continue; + } + // Skip monsters without melee attack + if( (react->desc->flags & hhReactionDesc::flagReq_MeleeAttack) && !AI_HAS_MELEE_ATTACK ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flagReq_KeyValue ) { + idStr val; + if( spawnArgs.GetString(react->desc->key, "", val) ) { + if( val != react->desc->keyVal ) { + continue; + } + } + else { + continue; + } + } + // Skip monsters without specific animation? + if( react->desc->flags & hhReactionDesc::flagReq_Anim ) { + if( !GetAnimator()->HasAnim(react->desc->anim) ) { + continue; + } + } + + // Skip monsters in vehicles? + if( (react->desc->flags & hhReactionDesc::flagReq_NoVehicle) && InVehicle() ) { + continue; + } + + float distToEnt, distToEnemy, distEnemyToEnt; + // Check actual specifics for reaction type + switch( react->desc->effect ) { + case hhReactionDesc::Effect_HaveFun: + case hhReactionDesc::Effect_Vehicle: + case hhReactionDesc::Effect_VehicleDock: + case hhReactionDesc::Effect_ProvideCover: + //only use reasonably reachable objects + if ( enemy.IsValid() ) { + distToEnt = TravelDistance( GetOrigin(), ent->GetOrigin() ); + distToEnemy = (GetOrigin() - enemy->GetOrigin()).Length(); + distEnemyToEnt = (enemy->GetOrigin() - ent->GetOrigin()).Length(); + if ( distToEnt > 1.0 ) { + float ratio = distToEnt / distEnemyToEnt; + if ( ratio > 2.0f ) { + //skip if object is farther than enemy or if enemy is closer to object than i am + continue; + } + } + } + case hhReactionDesc::Effect_Heal: + if ( enemy.IsValid() ) { + distToEnt = TravelDistance( GetOrigin(), ent->GetOrigin() ); + distToEnemy = (GetOrigin() - enemy->GetOrigin()).Length(); + distEnemyToEnt = (enemy->GetOrigin() - ent->GetOrigin()).Length(); + if ( distToEnt > 1.0 ) { + float ratio = distToEnt / distEnemyToEnt; + if ( ratio > 2.0f ) { + //skip if object is farther than enemy or if enemy is closer to object than i am + continue; + } + } + } + case hhReactionDesc::Effect_Climb: + case hhReactionDesc::Effect_Passageway: + case hhReactionDesc::Effect_CallBackup: + break; + case hhReactionDesc::Effect_DamageEnemy: + //check if enemy is close enough to matter + if ( enemy.IsValid() && react->desc->effectRadius > 0 ) { + enemyToReact = (enemy->GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr(); + if ( enemyToReact > react->desc->effectRadius * react->desc->effectRadius ) { + continue; + } + } + //check if i'm too close + meToReact = (GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr(); + if ( meToReact < react->desc->effectRadius * react->desc->effectRadius ) { + continue; + } + break; + case hhReactionDesc::Effect_Damage: + default: + continue; + } + // if we have actually gotten this far, do our intense calculations last.. + int rank = EvaluateReaction( react ); + if( rank == 0 ) { + continue; + } + +// We have a valid reaction... + float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr(); + if ( bestRank == -1 || bestRank <= rank ) { // Check the reaction rank against our best rank + if (rank == bestRank && distSq > bestDistance) { // If they are the same rank, but the current one is farther then ignore it. + continue; + } + bestEnt = ent; + bestReactIndex = j; + bestDistance = distSq; + bestRank = rank; + } + } + } + + if ( bestEnt ) { + targetReaction.entity = bestEnt; + targetReaction.reactionIndex = bestReactIndex; + hhReaction *reaction = targetReaction.GetReaction(); + if( reaction && reaction->desc->flags & hhReactionDesc::flagReq_RangeAttack ) { + shootTarget = bestEnt; + } else { + shootTarget = NULL; + } + idThread::ReturnEntity( targetReaction.entity.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhHunterSimple::AnimMove( void ) { + //overridden to set AI_BLOCKED when blockEnt is found + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + nextMovePos = newDest; + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, physicsObj.GetOrigin(), physicsObj.GetOrigin() + delta.ToNormal() * 100, 10 ); + gameRenderWorld->DebugArrow( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + renderEntity.axis[1] * 100, 10 ); + gameRenderWorld->DebugArrow( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + GetPhysics()->GetAxis()[2] * 100, 10 ); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt ) { + AI_BLOCKED = true; + AI_NEXT_DIR_TIME = 0; + + if ( blockEnt->IsType( idAI::Type ) ) { + StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER ); + } + } + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhHunterSimple::Event_AssignSniperFx( hhEntityFx* fx ) { + sniperFx = fx; +} + +void hhHunterSimple::Event_AssignFlashLightFx( hhEntityFx* fx ) { + flashLightFx = fx; +} + +bool hhHunterSimple::UpdateAnimationControllers( void ) { + idVec3 local; + idVec3 focusPos; + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idAngles diff; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idAFAttachment *headEnt = head.GetEntity(); + idVec3 eyepos; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + + if ( AI_DEAD ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 ); + } + + if ( focusJoint != INVALID_JOINT ) { + if ( headEnt ) { + headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } else { + // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that? + GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } + eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; + } else { + eyepos = GetEyePosition(); + } + + if ( headEnt ) { + CopyJointsFromBodyToHead(); + } + + // 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(); + + idEntity *focusEnt = focusEntity.GetEntity(); + //HUMANHEAD jsh allow eyefocus independent from allowJointMod + if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) ) { + focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; + } else if ( focusEnt == NULL ) { + // keep looking at last position until focusTime is up + focusPos = currentFocusPos; + } else if ( focusEnt == enemy.GetEntity() ) { + if ( beamLaser.IsValid() && beamLaser->IsActivated() ) { + focusPos = beamLaser->GetTargetLocation(); + } else { + focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); + } + } else if ( focusEnt->IsType( idActor::Type ) ) { + focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + focusPos = focusEnt->GetPhysics()->GetOrigin(); + } + + currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; + + // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles + dir = focusPos - orientationJointPos; + newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); + newLookAng.roll = 0.0f; + newLookAng.pitch = 0.0f; + newLookAng += lookOffset; + + // determine pitch from joint position + dir = focusPos - eyepos; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 ); + } + dir.NormalizeFast(); + physicsObj.GetAxis().ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch; + newLookAng.roll = 0.0f; + diff = newLookAng - lookAng; + + if ( eyeAng != diff ) { + eyeAng = diff; + eyeAng.Clamp( eyeMin, eyeMax ); + idAngles angDelta = diff - eyeAng; + if ( !angDelta.Compare( ang_zero, 0.1f ) ) { + alignHeadTime = gameLocal.time; + } else { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + } + } + + if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { + alignHeadTime = gameLocal.time; + } + + if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + destLookAng = newLookAng; + destLookAng.Clamp( lookMin, lookMax ); + } + + diff = destLookAng - lookAng; + if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { + if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { + diff.pitch = 360.0f - diff.pitch; + } + } + if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { + if ( diff.yaw > 180.0f ) { + diff.yaw -= 360.0f; + } else if ( diff.yaw <= -180.0f ) { + diff.yaw += 360.0f; + } + } + lookAng = lookAng + diff * headFocusRate; + lookAng.Normalize180(); + + jointAng.roll = 0.0f; + if ( allowJointMod ) { + //quick hack. dont change yaw if in different gravity + bool oriented = GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1 ); + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( oriented ) { + jointAng.yaw = 0; + } else { + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + } + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + + if ( move.moveType == MOVETYPE_FLY ) { + // lean into turns + AdjustFlyingAngles(); + } + + if ( headEnt ) { + idAnimator *headAnimator = headEnt->GetAnimator(); + + // HUMANHEAD pdm: Added support for look joints in head entities + if ( allowJointMod ) { + for( i = 0; i < headLookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw; + headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + // HUMANHEAD END + + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); + headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); + + //if ( ai_debugMove.GetBool() ) { + // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec ); + //} + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); + animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); + animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + + //HUMANHEAD pdm jawflap + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + //END HUMANHEAD + + return true; +} + +void hhHunterSimple::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //overridden to remove muzzle fx on death + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + if ( enemy.IsValid() ) { + gameLocal.AlertAI( enemy.GetEntity(), spawnArgs.GetInt( "death_alert_radius", "800" ) ); + } + Event_FlashLightOff(); + if ( beamLaser.IsValid() ) { + beamLaser->Unbind(); + SAFE_REMOVE( beamLaser ); + } + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + //taken from idEntity::RemoveBinds() + //remove any lingering fx on death + idEntity *ent; + idEntity *next; + for( ent = GetTeamChain(); ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent->GetBindMaster() == this && ent->IsType( idEntityFx::Type ) ) { + ent->Unbind(); + if( !ent->fl.noRemoveWhenUnbound ) { + ent->PostEventMS( &EV_Remove, 0 ); + if (gameLocal.isClient) { + ent->Hide(); + } + } + next = GetTeamChain(); + } + } + +} + +void hhHunterSimple::Save( idSaveGame *savefile ) const { + beamLaser.Save( savefile ); + savefile->WriteVec3( nextMovePos ); + savefile->WriteAngles( kickAngles ); + savefile->WriteAngles( kickSpeed ); + savefile->WriteFloat( flashlightLength ); + sniperFx.Save( savefile ); + + ally.Save( savefile ); + muzzleFx.Save( savefile ); + flashLightFx.Save( savefile ); + savefile->WriteBool( bFlashLight ); + savefile->WriteInt( endSpeechTime ); + savefile->WriteInt( nextEnemyCheck ); + savefile->WriteFloat( lastEnemyDistance ); + savefile->WriteInt( lastChargeTime ); + savefile->WriteInt( nextVoiceTime ); + savefile->WriteVec3( initialOrigin ); + savefile->WriteInt( flashlightTime ); + enemyPortal.Save( savefile ); + savefile->WriteVec3( lastMoveOrigin ); + savefile->WriteInt( nextBlockCheckTime ); + savefile->WriteInt( nextSpiritCheck ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + + //HUMANHEAD jsh PCF 4/28/06 save nodelist + savefile->WriteInt(nodeList.Num()); + for (int i = 0; i < nodeList.Num(); i++) { + nodeList[i].Save(savefile); + } + //END HUMANHEAD +}; + +void hhHunterSimple::Restore( idRestoreGame *savefile ) { + beamLaser.Restore( savefile ); + savefile->ReadVec3( nextMovePos ); + savefile->ReadAngles( kickAngles ); + savefile->ReadAngles( kickSpeed ); + savefile->ReadFloat( flashlightLength ); + sniperFx.Restore( savefile ); + + ally.Restore( savefile ); + muzzleFx.Restore( savefile ); + flashLightFx.Restore( savefile ); + savefile->ReadBool( bFlashLight ); + savefile->ReadInt( endSpeechTime ); + savefile->ReadInt( nextEnemyCheck ); + savefile->ReadFloat( lastEnemyDistance ); + savefile->ReadInt( lastChargeTime ); + savefile->ReadInt( nextVoiceTime ); + savefile->ReadVec3( initialOrigin ); + savefile->ReadInt( flashlightTime ); + enemyPortal.Restore( savefile ); + savefile->ReadVec3( lastMoveOrigin ); + savefile->ReadInt( nextBlockCheckTime ); + savefile->ReadInt( nextSpiritCheck ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + + //HUMANHEAD jsh PCF 4/28/06 save nodelist + int num; + savefile->ReadInt(num); + nodeList.SetNum(num); + for (int i = 0; i < num; i++) { + nodeList[i].Restore(savefile); + } + //END HUMANHEAD + + alternateAccuracy = false; + enemyRushCount = 0; + enemyRetreatCount = 0; + enemyHiddenCount = 0; + currentAction.Clear(); + currentSpeech.Clear(); +}; + +void hhHunterSimple::Event_FlashLightOn() { + if ( !bFlashLight && gameLocal.time > flashlightTime && spawnArgs.GetBool( "can_flashlight" ) ) { + bFlashLight = true; + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_flashLight"), "fx_barrel", NULL, &MA_AssignFlashLightFx, false ); + } +} + +void hhHunterSimple::Event_FlashLightOff() { + bFlashLight = false; + SAFE_REMOVE( flashLightFx ); +} + +void hhHunterSimple::PrintDebug() { + hhMonsterAI::PrintDebug(); + gameLocal.Printf( " Ally: %s\n", ally.IsValid() ? ally->GetName() : "none" ); + gameLocal.Printf( " Allow Movement: %s\n", allowMove ? "yes" : "no" ); + gameLocal.Printf( " Turn rate : %f\n", turnRate ); + if ( nodeList.Num() ) { + gameLocal.Printf( " Cover Points:\n" ); + aasPath_t path; + int toAreaNum, areaNum; + for( int i=0;iGetOrigin() ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, nodeList[i]->GetOrigin() ) ) { + common->Printf( " %s: " S_COLOR_RED "NOT REACHABLE\n", nodeList[i]->GetName() ); + } else { + common->Printf( " %s: " S_COLOR_GREEN " REACHABLE\n", nodeList[i]->GetName() ); + } + } + } + } +} + +bool hhHunterSimple::TurnToward( const idVec3 &pos ) { + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + + if (AI_VEHICLE && InVehicle()) { + GetVehicleInterfaceLocal()->OrientTowards( pos, 0.5 ); + return true; + } + + dir = pos - physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + physicsObj.GetAxis().ProjectVector( dir, local_dir ); +#else + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); +#endif + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) { + //HUMANHEAD jsh PCF 4/29/06 changed to use enemy rather than player for directional movement + if ( !AI_DIR_MOVEMENT || AI_PATHING || !enemy.IsValid() ) { + AI_DIR_MOVEMENT = false; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + AI_DIR = HUNTER_N; + } else { + float toEnemy = (enemy->GetOrigin() - GetOrigin()).ToAngles().yaw; + float toPos = (pos - GetOrigin()).ToAngles().yaw; + if ( gameLocal.time > AI_NEXT_DIR_TIME && lengthSqr > Square( 2.0f ) ) { + float ang = toEnemy - toPos; + ang = idMath::AngleNormalize360( ang ); + AI_NEXT_DIR_TIME = gameLocal.time + SEC2MS(1); + if ( ang >= 135 && ang < 225 ) { + AI_DIR = HUNTER_S; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 180 ); + } else if ( ang >= 45 && ang < 135 ) { + AI_DIR = HUNTER_W; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 90 ); + } else if ( ang >= 225 && ang < 315 ) { + AI_DIR = HUNTER_E; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() + 270 ); + } else { + AI_DIR = HUNTER_N; + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + } + } + } + } + + bool result = FacingIdeal(); + return result; +} + +void hhHunterSimple::LinkScriptVariables( void ) { + hhMonsterAI::LinkScriptVariables(); + AI_DIR.LinkTo( scriptObject, "AI_DIR" ); + AI_DIR_MOVEMENT.LinkTo( scriptObject, "AI_DIR_MOVEMENT" ); + AI_LAST_DAMAGE_TIME.LinkTo( scriptObject, "AI_LAST_DAMAGE_TIME" ); + AI_ENEMY_RUSH.LinkTo( scriptObject, "AI_ENEMY_RUSH" ); + AI_ENEMY_RETREAT.LinkTo( scriptObject, "AI_ENEMY_RETREAT" ); + AI_ENEMY_HEALTH_LOW.LinkTo( scriptObject, "AI_ENEMY_HEALTH_LOW" ); + AI_ENEMY_RESURRECTED.LinkTo( scriptObject, "AI_ENEMY_RESURRECTED" ); + AI_ALLOW_ORDERS.LinkTo( scriptObject, "AI_ALLOW_ORDERS" ); + AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" ); + AI_ENEMY_SHOOTABLE.LinkTo( scriptObject, "AI_ENEMY_SHOOTABLE" ); + AI_ENEMY_LAST_SEEN.LinkTo( scriptObject, "AI_ENEMY_LAST_SEEN" ); + AI_SHOTBLOCK.LinkTo( scriptObject, "AI_SHOTBLOCK" ); + AI_NEXT_DIR_TIME.LinkTo( scriptObject, "AI_NEXT_DIR_TIME" ); + AI_BLOCKED_FAILSAFE.LinkTo( scriptObject, "AI_BLOCKED_FAILSAFE" ); + AI_KNOCKBACK.LinkTo( scriptObject, "AI_KNOCKBACK" ); +} + +void hhHunterSimple::Event_GetAlly() { + if ( ally.IsValid() ) { + idEntity *foo = ally.GetEntity(); + if ( ally->GetHealth() > 0 ) { + idThread::ReturnEntity( ally.GetEntity() ); + return; + } else { + ally.Clear(); + } + } + + idBounds bo( GetOrigin() ); + bo.ExpandSelf( 2000 ); + hhHunterSimple *entAI = NULL; + idLocationEntity *myLoc = NULL; + idLocationEntity *entLoc = NULL; + float bestDistSq = 9999999; + float distSq = 0; + entAI = static_cast(gameLocal.FindEntity( spawnArgs.GetString( "ally" ) )); + if ( entAI && entAI->ally.IsValid() ) { + entAI = NULL; + } + if ( !entAI ) { + hhHunterSimple *hunter = NULL; + for ( int i=0;i(hhMonsterAI::allSimpleMonsters[i]); + if ( !hunter || hunter == this || hunter->health <= 0 ) { + continue; + } + if ( !hunter->IsType( hhHunterSimple::Type ) || hunter->IsHidden() || hunter->ally.IsValid() ) { + continue; + } + if ( !hunter->GetEnemy() || hunter->GetEnemy() != GetEnemy() ) { + continue; + } + + //if both locations are null, it likely means this locations haven't been set + //i.e. dev test map so just let the hunters to ally + myLoc = gameLocal.LocationForPoint( GetOrigin() ); + entLoc = gameLocal.LocationForPoint( hunter->GetOrigin() ); + if ( myLoc != entLoc ) { + continue; + } + + distSq = (GetOrigin() - hunter->GetOrigin()).LengthSqr(); + if ( distSq > 2250000 ) { //less than 1500 + continue; + } + if ( distSq < bestDistSq ) { + entAI = hunter; + bestDistSq = distSq; + } + } + } + if ( entAI ) { + ally.Assign( entAI ); + entAI->ally.Assign( this ); + idThread::ReturnEntity( ally.GetEntity() ); + return; + } else { + ally.Clear(); + idThread::ReturnEntity( NULL ); + return; + } +} + +void hhHunterSimple::Event_PrintAction( const char *action ) { + currentAction = action; +} + +void hhHunterSimple::Event_EnemyCanSee() { + trace_t tr; + if ( !enemy.IsValid() ) { + idThread::ReturnInt( false ); + return; + } + + // Check if a trace succeeds from the player to the entity + gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), GetEyePosition(), MASK_MONSTERSOLID, enemy.GetEntity() ); + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhHunterSimple::Event_TriggerDelay( idEntity *ent, float delay ) { + if ( !ent ) { + return; + } + if ( delay < 0.0f ) { + delay = 0.0f; + } + ent->PostEventSec( &EV_Activate, delay, this ); +} + +void hhHunterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool( "pain_knockback", "0" ) ) { + AI_KNOCKBACK = true; + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + AI_LAST_DAMAGE_TIME = MS2SEC(gameLocal.time); +} + +void hhHunterSimple::Event_CallBackup( float delay ) { + idEntity *entAI = gameLocal.FindEntity( spawnArgs.GetString( "backup" ) ); + if ( entAI && entAI->IsType( idAI::Type ) && entAI->spawnArgs.GetBool( "portal", "0" ) ) { + if ( delay < 0.0f ) { + delay = 0.0f; + } + entAI->PostEventSec( &EV_Activate, delay, this ); + } +} + +bool hhHunterSimple::CanSee( idEntity *ent, bool useFov ) { + trace_t tr; + idVec3 eye; + idVec3 toPos; + + if ( ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + idActor *act = static_cast(ent); + + // If this actor is in a vehicle, look at the vehicle, not the actor + if(act->InVehicle()) { + ent = act->GetVehicleInterface()->GetVehicle(); + } + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ( ( idActor * )ent )->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetOrigin(); + } + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + + eye = GetEyePosition(); + + if ( InVehicle() ) { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, GetVehicleInterface()->GetVehicle() ); // HUMANHEAD JRM + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + } else { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); // HUMANHEAD JRM + + //if enemy is close to where the trace hit, he is shootable + if ( tr.fraction < 1.0f && enemy.IsValid() ) { + float hitDist = (GetEyePosition() - tr.endpos).LengthFast(); + float fullDist = (enemy->GetEyePosition() - GetEyePosition()).LengthFast(); + if ( fullDist > 0.0 ) { + if ( hitDist / fullDist > 0.75f ) { + AI_ENEMY_SHOOTABLE = true; + } else { + AI_ENEMY_SHOOTABLE = false; + } + } + } + + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } else if ( bSeeThroughPortals && aas ) { + shootTarget = NULL; +#if 0 // jmarshall AI + int myArea = gameRenderWorld->PointInArea( GetOrigin() ); + int numPortals = gameRenderWorld->NumGamePortalsInArea( myArea ); + if ( numPortals > 0 ) { + int enemyArea = gameRenderWorld->PointInArea( ent->GetOrigin() ); + for ( int i=0;iGetSoundPortal( myArea, i ).areas[0] == enemyArea ) { + //find the portal and set it as this monster's shoottarget + idEntity *spawnedEnt = NULL; + for( spawnedEnt = gameLocal.spawnedEntities.Next(); spawnedEnt != NULL; spawnedEnt = spawnedEnt->spawnNode.Next() ) { + if ( !spawnedEnt->IsType( hhPortal::Type ) ) { + continue; + } + if ( gameRenderWorld->PointInArea( spawnedEnt->GetOrigin() ) == myArea) { + shootTarget = spawnedEnt; + return true; + } + } + } + } + } +#endif + } + } + + return false; +} + +void hhHunterSimple::Event_SaySound( const char *soundName ) { + if ( AI_DEAD || ai_skipSpeech.GetBool() || gameLocal.time < nextVoiceTime ) { + return; + } + //if we've got an ally, clear out this sound so that he won't say it + if ( ally.IsValid() ) { + ally->spawnArgs.Set( soundName, "" ); + } + int time; + StartSound( soundName, ( s_channelType )SND_CHANNEL_VOICE2, 0, 0, &time ); + idThread::ReturnFloat( MS2SEC( time ) ); +} + +void hhHunterSimple::Event_OnProjectileLand(hhProjectile *proj) { + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_AvoidGrenade" ); + shootTarget = proj; + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhHunterSimple::Event_CheckRush() { + if ( enemy.IsValid() ) { + float currentDist = (enemy->GetOrigin() - GetOrigin()).Length(); + if ( currentDist < lastEnemyDistance ) { + //enemy is advancing + enemyRushCount++; + if ( enemyRushCount > spawnArgs.GetFloat( "charge_threshold", "3" ) ) { + if ( currentDist < spawnArgs.GetFloat( "rush_distance", "400" ) ) { + AI_ENEMY_RUSH = true; + } + } + } else { + enemyRushCount = 0; + AI_ENEMY_RUSH = false; + } + lastEnemyDistance = currentDist; + } + PostEventSec( &MA_CheckRush, spawnArgs.GetFloat( "rush_freq", "0.5" ) ); +} + +void hhHunterSimple::Event_SetNextVoiceTime( int nextTime ) { + nextVoiceTime = nextTime; +} + +void hhHunterSimple::Event_CheckRetreat() { + if ( enemy.IsValid() ) { + float currentDist = (enemy->GetOrigin() - GetOrigin()).Length(); + if ( currentDist > lastEnemyDistance ) { + //enemy is retreating + enemyRetreatCount++; + if ( enemyRetreatCount > spawnArgs.GetFloat( "retreat_threshold", "3" ) ) { + AI_ENEMY_RETREAT = true; + } + } else { + enemyRetreatCount = 0; + AI_ENEMY_RETREAT = false; + } + } + PostEventSec( &MA_CheckRetreat, spawnArgs.GetFloat( "retreat_freq", "0.5" ) ); +} + +void hhHunterSimple::Event_SayEnemyInfo() { + if ( AI_DEAD || !enemy.IsValid() || !ally.IsValid() || gameLocal.time < nextVoiceTime ) { + idThread::ReturnInt( false ); + return; + } + hhHunterSimple *hunterAlly = static_cast(ally.GetEntity()); + if ( !hunterAlly ) { + idThread::ReturnInt( false ); + return; + } + bool closerToEnemy = false; + idVec3 meToEnemy = enemy->GetOrigin() - GetOrigin(); + idVec3 allyToEnemy = enemy->GetOrigin() - ally->GetOrigin(); + float myDistToEnemy = meToEnemy.LengthFast(); + float allyDistToEnemy = allyToEnemy.LengthFast(); + float distToAlly = (ally->GetOrigin() - GetOrigin()).LengthFast(); + if ( myDistToEnemy < allyDistToEnemy ) { + closerToEnemy = true; + } + if ( !CanSee( enemy.GetEntity(), false ) ) { + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + Event_SaySound( "snd_behind_cover" ); + idThread::ReturnInt( true ); + return; + } + if ( closerToEnemy ) { + //check if enemy is underneath + if ( allyToEnemy.z < -spawnArgs.GetFloat( "z_down_dist", "75" ) ) { + if ( idMath::Fabs(meToEnemy.z) < spawnArgs.GetFloat( "z_equal_dist", "40" ) ) { + Event_SaySound( "snd_down_here" ); + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + idThread::ReturnInt( true ); + return; + } + } + if ( myDistToEnemy < spawnArgs.GetFloat( "over_there_dist", "400" ) ) { + Event_SaySound( "snd_over_here" ); + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + idThread::ReturnInt( true ); + return; + } + } else { + if ( myDistToEnemy > spawnArgs.GetFloat( "over_there_dist", "400" ) ) { + Event_SaySound( "snd_over_there" ); + hunterAlly->SetNextVoiceTime( gameLocal.time + SEC2MS(1) ); + idThread::ReturnInt( true ); + return; + } + } + idThread::ReturnInt( false ); +} + +bool hhHunterSimple::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. + assert( idStr::Icmpn( soundName, "snd_", 4 ) == 0 ); + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( sound[0] == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + // HUMANHEAD nla - Check if this sound should be played every X seconds + float minInterval; + int lastPlayed; + if ( spawnArgs.GetFloat( va( "min_%s", soundName ), "0", minInterval ) && minInterval > 0 ) { + if ( GetLastTimeSoundPlayed()->GetInt( soundName, "-1", lastPlayed ) ) { + if ( gameLocal.time - minInterval * 1000 < lastPlayed ) { + return( false ); + } + } + GetLastTimeSoundPlayed()->SetInt( soundName, gameLocal.time ); + } else if ( channel == SND_CHANNEL_VOICE2 ) { + //only play voice over sounds once for the hunter + if ( GetLastTimeSoundPlayed()->GetInt( soundName, "-1", lastPlayed ) ) { + if ( lastPlayed > 0 ) { + return( false ); + } + } + GetLastTimeSoundPlayed()->SetInt( soundName, gameLocal.time ); + } + // HUMANHEAD END + + shader = declManager->FindSound( sound ); + bool result; + result = StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); + if ( result && shader && channel == SND_CHANNEL_VOICE2 ) { + //print debug info for voice overs + currentSpeech = shader->GetName(); + endSpeechTime = gameLocal.time + SEC2MS(3); + } + return result; +} + +void hhHunterSimple::Event_GetCoverNode() { + float dist, bestDist; + idEntity *bestNode = NULL; + idEntity *node = NULL; + if ( !enemy.IsValid() || !EnemyPositionValid() ) { + idThread::ReturnEntity( NULL ); + return; + } + + //find the closest attack node that can see our enemy + bestNode = NULL; + bestDist = 9999999.0f; + idLocationEntity *myLocation = gameLocal.LocationForPoint( physicsObj.GetOrigin() ); + idLocationEntity *entLocation = NULL; + for( int i = 0; i < nodeList.Num(); i++ ) { + if ( !nodeList[i].IsValid() ) { + continue; + } + if ( !nodeList[i]->IsType( hhAINode::Type ) ) { + continue; + } + node = nodeList[ i ].GetEntity(); + if ( !node ) { + continue; + } + + //check if node is used + if ( node->IsType( hhAINode::Type ) ) { + hhAINode *aiNode = static_cast(node); + if ( aiNode && aiNode->user.IsValid() && !aiNode->user.IsEqualTo( this ) ) { + if ( !aiNode->user->IsHidden() && aiNode->user->GetHealth() > 0 ) { + continue; + } else { + aiNode->user.Clear(); + } + } + } + + //skip if node is farther than enemy or enemy is closer to object than i am + float distToNode = TravelDistance( GetOrigin(), node->GetOrigin() ); + float distEnemyToNode = (enemy->GetOrigin() - node->GetOrigin()).Length(); + if ( distEnemyToNode < distToNode || distEnemyToNode < 200 ) { + continue; + } + //skip if object is farther than enemy or if enemy is closer to object than i am + if ( distToNode > 1.0 && distToNode / distEnemyToNode > 2.0f ) { + continue; + } + + //skip if enemy can see the node + trace_t tr; + gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), node->GetOrigin(), MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorMagenta, enemy->GetEyePosition(), node->GetOrigin(), 30, 5000 ); + } + continue; + } + + //find closest node + idVec3 org = node->GetPhysics()->GetOrigin(); + dist = ( physicsObj.GetOrigin() - org ).LengthSqr(); + if ( dist > bestDist ) { + continue; + } + + //found a node that passed all tests + bestNode = node; + bestDist = dist; + } + if ( bestNode && bestNode->IsType( hhAINode::Type ) ) { + hhAINode *bestAINode = static_cast(bestNode); + if ( bestAINode ) { + bestAINode->user.Assign( this ); + } + } + if ( bestNode ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, enemy->GetEyePosition(), bestNode->GetOrigin(), 30, 5000 ); + } + idThread::ReturnVector( bestNode->GetOrigin() ); + return; + } + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Event_GetCoverPoint() { + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + //couldn't find a node. try sampling some nearby points + idVec3 testPoint; + float bestDist = 9999999.0f; + idVec3 finalPoint = vec3_zero; + int finalArea = -1; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + int num; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idBounds bounds; + trace_t tr; + float distance = spawnArgs.GetFloat( "cover_range", "200" ); + float initialYaw = yaw; + for ( int i=0;i<16;i++ ) { + testPoint = GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + testPoint.z += 35; + yaw += 22.5; + + if( gameLocal.clip.Contents( testPoint, GetPhysics()->GetClipModel(), viewAxis, CONTENTS_SOLID, this ) != 0 ) { + continue; + } + + //skip if node is farther than enemy or enemy is closer to object than i am + float distToNode = TravelDistance( GetOrigin(), testPoint ); + float distEnemyToNode = (enemy->GetOrigin() - testPoint).Length(); + if ( distEnemyToNode < distToNode || distEnemyToNode < 200 ) { + continue; + } + //skip if object is farther than enemy or if enemy is closer to object than i am + if ( distToNode > 1.0 && distToNode / distEnemyToNode > 2.0f ) { + continue; + } + + //make sure it wont clip into anything at testPoint + clipped = false; + bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() ); + num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( int j = 0; j < num; j++ ) { + cm = clipModels[ j ]; + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + if ( physicsObj.ClipContents( cm ) ) { + clipped = true; + break; + } + } + if ( clipped ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 ); + } + continue; + } + + //make sure we can path there + aasPath_t path; + int toAreaNum = PointReachableAreaNum( testPoint ); + int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) { + continue; + } + + //make sure enemy cant see the point + gameLocal.clip.TracePoint( tr, enemy->GetEyePosition(), testPoint, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + continue; + } + + float distToPoint = (GetOrigin() - testPoint).Length(); + if ( distToPoint < bestDist ) { + finalPoint = testPoint; + bestDist = distToPoint; + finalArea = toAreaNum; + } + } + + if ( finalPoint != vec3_zero ) { + if ( aas ) { + aas->PushPointIntoAreaNum( finalArea, finalPoint ); + } + idThread::ReturnVector( finalPoint ); + return; + } + + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Event_GetSightNode() { + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + //find a good spot to attack from + trace_t tr; + float distance = spawnArgs.GetFloat( "attack_range", "500" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + float initialYaw = yaw; + idEntity *bestNode = NULL; + idEntity *node = NULL; + + //find an attack node that can see our enemy + for( int i = 0; i < nodeList.Num(); i++ ) { + if ( !nodeList[i].IsValid() ) { + continue; + } + if ( !nodeList[i]->IsType( hhAINode::Type ) ) { + continue; + } + node = nodeList[ i ].GetEntity(); + if ( !node ) { + continue; + } + + //check if node is used + if ( node->IsType( hhAINode::Type ) ) { + hhAINode *aiNode = static_cast(node); + if ( aiNode && aiNode->user.IsValid() && !aiNode->user.IsEqualTo( this ) ) { + if ( !aiNode->user->IsHidden() && aiNode->user->GetHealth() > 0 ) { + continue; + } else { + aiNode->user.Clear(); + } + } + } + + //check if we can see enemy from this node + idVec3 eye = testPoint + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); + //HUMANHEAD jsh PCF 4/27/06 changed trace to end at enemy origin instead of enemy eye position to fix slowdown + gameLocal.clip.TracePoint( tr, eye, enemy->GetOrigin(), MASK_SHOT_BOUNDINGBOX, enemy.GetEntity() ); + if ( tr.fraction < 1.0f || ( gameLocal.GetTraceEntity( tr ) != enemy.GetEntity() ) ) { + continue; + } + + //found a node that passed all tests + bestNode = node; + } + if ( bestNode && bestNode->IsType( hhAINode::Type ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), bestNode->GetOrigin(), 10, 5000 ); + } + idThread::ReturnVector( bestNode->GetOrigin() ); + return; + } + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Event_GetNearSightPoint() { + trace_t tr; + float distance = spawnArgs.GetFloat( "attack_range", "500" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = 0; + float initialYaw = 0; + int num, i, j; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idBounds bounds; + int finalArea = -1; + idList listo; + + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + initialYaw = yaw; + //test 8 points around the monster, starting with one directly in front of it + for ( i=0;i<32;i++ ) { + testPoint = GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + testPoint.z += spawnArgs.GetFloat( "attack_z", "300" ); + yaw += 11.25; + + //make sure it wont clip into anything at testPoint + clipped = false; + bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() ); + num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( j = 0; j < num; j++ ) { + cm = clipModels[ j ]; + if ( cm->IsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + if ( physicsObj.ClipContents( cm ) ) { + clipped = true; + break; + } + } + if ( clipped ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 ); + } + continue; + } + + //make sure we can path there + int toAreaNum = PointReachableAreaNum( testPoint ); + int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + aasPath_t path; + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, GetOrigin(), testPoint, 10, 5000 ); + } + continue; + } + + //make sure we can see enemy there + idVec3 eye = testPoint + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); + gameLocal.clip.TracePoint( tr, eye, enemy->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, enemy.GetEntity() ); + if ( tr.fraction < 1.0f ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, eye, enemy->GetEyePosition(), 10, 5000 ); + } + continue; + } + + listo.Append( testPoint ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), testPoint, 10, 5000 ); + } + finalArea = toAreaNum; + } + if ( listo.Num() ) { + finalPoint = listo[gameLocal.random.RandomInt(listo.Num())]; + } + if ( finalPoint != vec3_zero ) { + if ( aas && finalArea != -1 ) { + aas->PushPointIntoAreaNum( finalArea, finalPoint ); + } + idThread::ReturnVector( finalPoint ); + return; + } + idThread::ReturnVector( vec3_zero ); +} + +void hhHunterSimple::Show() { + flashlightTime = gameLocal.time + SEC2MS( spawnArgs.GetInt( "flashlight_delay" ) ); + hhMonsterAI::Show(); +} + +void hhHunterSimple::Event_EnemyPortal( idEntity *ent ) { + //HUMANHEAD jsh PCF 4/27/06 Added check for enemy and hiddenness + if ( !enemy.IsValid() || AI_PATHING || fl.hidden ) { + return; + } + enemyPortal = ent; + SetState( GetScriptFunction( "state_PortalAttack" ) ); +} + +void hhHunterSimple::Event_GetEnemyPortal() { + if ( enemyPortal.IsValid() ) { + idThread::ReturnEntity( enemyPortal.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhHunterSimple::Event_OnProjectileHit(hhProjectile *proj) { + if ( !enemy.IsValid() || !proj || AI_VEHICLE ) { + return; + } + if ( ( proj->GetOrigin().ToVec2() - GetOrigin().ToVec2() ).LengthFast() < 150 ) { + AI_SHOTBLOCK = true; + } +} + +void hhHunterSimple::BlockedFailSafe() { + if ( move.moveCommand < NUM_NONMOVING_COMMANDS || !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f || !enemy.IsValid() || AI_PATHING ) { + return; + } + if ( gameLocal.time > nextBlockCheckTime ) { + if ( ( lastMoveOrigin - physicsObj.GetOrigin() ).Length() < blockedRadius ) { + AI_BLOCKED_FAILSAFE = true; + } + lastMoveOrigin = physicsObj.GetOrigin(); + nextBlockCheckTime = gameLocal.time + blockedMoveTime; + } +} +void hhHunterSimple::UpdateEnemyPosition( void ) { + idActor *enemyEnt = enemy.GetEntity(); + int enemyAreaNum; + int areaNum; + aasPath_t path; + predictedPath_t predictedPath; + idVec3 enemyPos; + bool onGround; + + if ( !enemyEnt ) { + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + if ( move.moveType == MOVETYPE_FLY ) { + enemyPos = enemyEnt->GetPhysics()->GetOrigin(); + onGround = true; + } else { + onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos ); + if ( enemyEnt->OnLadder() ) { + onGround = false; + } + } + + if ( onGround ) { + // when we don't have an AAS, we can't tell if an enemy is reachable or not, + // so just assume that he is. + if ( !aas ) { + enemyAreaNum = 0; + lastReachableEnemyPos = enemyPos; + } else { + enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f ); + if ( enemyAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) { + lastReachableEnemyPos = enemyPos; + } + } + } + } + + AI_ENEMY_IN_FOV = false; + AI_ENEMY_VISIBLE = false; + + if ( CanSee( enemyEnt, false ) ) { + AI_ENEMY_VISIBLE = true; + if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) { + AI_ENEMY_IN_FOV = true; + } + + SetEnemyPosition(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec ); + } +} + +//HUMANHEAD jsh PCF 4/27/06 increased the upper z zalue of the bounds to 90.0f +bool hhHunterSimple::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const { + if ( move.moveType == MOVETYPE_SLIDE ) { + idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } else { + if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) { + if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { + return true; + } + } else { + idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 90.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } + } + return false; +} + +void hhHunterSimple::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + HH_ASSERT( player == enemy.GetEntity() ); + //HUMANHEAD jsh PCF 4/29/06 stop looking and make enemy not visible for a frame + Event_LookAtEntity( NULL, 0.0f ); + AI_ENEMY_VISIBLE = false; + enemy = proxy; + nextSpiritCheck = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "spirit_check_delay", "5" ) ); +} + +void hhHunterSimple::Distracted( idActor *newEnemy ) { + if ( newEnemy && newEnemy->IsType( hhTalon::Type ) ) { + SetState( GetScriptFunction( "state_TalonCombat" ) ); + } else { + SetState( GetScriptFunction( "state_Combat" ) ); + } + + SetEnemy( newEnemy ); +} + +void hhHunterSimple::Event_EnemyVehicleDocked() { + if ( enemy.IsValid() && enemy->InVehicle() && enemy->GetVehicleInterface() ) { + hhVehicle *vehicle = enemy->GetVehicleInterface()->GetVehicle(); + if ( vehicle && vehicle->IsDocked() ) { + idThread::ReturnInt( true ); + return; + } + } + idThread::ReturnInt( false ); +} + +void hhHunterSimple::Event_Blocked() { + AI_NEXT_DIR_TIME = 0; + StopMove( MOVE_STATUS_DONE ); +} \ No newline at end of file diff --git a/neo/prey/Prey/ai_hunter_simple.h b/neo/prey/Prey/ai_hunter_simple.h new file mode 100644 index 00000000..7fe29aec --- /dev/null +++ b/neo/prey/Prey/ai_hunter_simple.h @@ -0,0 +1,128 @@ + +#ifndef __PREY_AI_HUNTER_SIMPLE_H__ +#define __PREY_AI_HUNTER_SIMPLE_H__ + +#define HUNTER_N 0 +#define HUNTER_NE 1 +#define HUNTER_E 2 +#define HUNTER_SE 3 +#define HUNTER_S 4 +#define HUNTER_SW 5 +#define HUNTER_W 6 +#define HUNTER_NW 7 + +class hhHunterSimple : public hhMonsterAI { + +public: + + CLASS_PROTOTYPE(hhHunterSimple); + + hhHunterSimple(); + void Event_OnProjectileLaunch(hhProjectile *proj); + void Event_OnProjectileHit(hhProjectile *proj); + void Event_LaserOn(); + void Event_LaserOff(); + void Event_FindReaction( const char* effect ); + + void Spawn(); + void Think(); + idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void AnimMove(); + bool UpdateAnimationControllers(); + 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 PrintDebug(); + virtual void LinkScriptVariables(); + bool TurnToward( const idVec3 &pos ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void SetNextVoiceTime( int newTime ) { nextVoiceTime = newTime; } + void Show(); + void BlockedFailSafe(); + void UpdateEnemyPosition(); + void Distracted( idActor *newEnemy ); + bool ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const; + + void Event_EscapePortal(); + void Event_AssignSniperFx( hhEntityFx* fx ); + void Event_AssignMuzzleFx( hhEntityFx* fx ); + void Event_AssignFlashLightFx( hhEntityFx* fx ); + void Event_KickAngle( const idAngles &ang ); + void Event_FlashLightOn(); + void Event_FlashLightOff(); + void Event_PostSpawn(); + void Event_EnemyCanSee(); + void Event_PrintAction( const char *action ); + void Event_GetAlly(); + void Event_TriggerDelay( idEntity *ent, float delay ); + void Event_CallBackup( float delay ); + virtual bool CanSee( idEntity *ent, bool useFov ); + void Event_GetAdvanceNode(); + void Event_GetRetreatNode(); + void Event_OnProjectileLand(hhProjectile *proj); + void Event_SaySound( const char *soundName ); + void Event_CheckRush(); + void Event_CheckRetreat(); + void Event_SayEnemyInfo(); + void Event_SetNextVoiceTime( int nextTime ); + void Event_GiveOrders( const char *orders ); + void Event_AllowOrders( bool orders ); + void Event_GetCoverNode(); + void Event_GetCoverPoint(); + void Event_GetSightNode(); + void Event_GetNearSightPoint(); + void Event_Blocked(); + void Event_EnemyPortal( idEntity *portal ); + void Event_GetEnemyPortal(); + void Event_EnemyVehicleDocked(); + void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes + + idEntityPtr ally; + idScriptBool AI_ALLOW_ORDERS; + idScriptBool AI_SHOTBLOCK; +protected: + idVec3 nextMovePos; + idEntityPtr beamLaser; + idAngles kickAngles; + idAngles kickSpeed; + idEntityPtr sniperFx; + idEntityPtr muzzleFx; + idEntityPtr flashLightFx; + idList< idEntityPtr< idEntity > > nodeList; + bool alternateAccuracy; + bool bFlashLight; + idScriptInt AI_DIR; + idScriptFloat AI_LAST_DAMAGE_TIME; + idStr currentAction; + idStr currentSpeech; + int endSpeechTime; + idScriptBool AI_DIR_MOVEMENT; + idScriptBool AI_ENEMY_RUSH; + idScriptBool AI_ENEMY_RETREAT; + idScriptBool AI_ENEMY_HEALTH_LOW; + idScriptBool AI_ENEMY_RESURRECTED; + idScriptBool AI_ONGROUND; + idScriptBool AI_ENEMY_SHOOTABLE; + idScriptBool AI_BLOCKED_FAILSAFE; + idScriptInt AI_ENEMY_LAST_SEEN; + idScriptInt AI_NEXT_DIR_TIME; + idScriptBool AI_KNOCKBACK; + int nextEnemyCheck; + float lastEnemyDistance; + int enemyRushCount; + int enemyRetreatCount; + int enemyHiddenCount; + int lastChargeTime; + int nextVoiceTime; + idVec3 initialOrigin; + float flashlightLength; + int flashlightTime; + idEntityPtr enemyPortal; + idVec3 lastMoveOrigin; + int nextBlockCheckTime; + int nextSpiritCheck; //HUMANHEAD jsh PCF 5/2/06 hunter combat fixes +}; + +#endif /* __PREY_AI_HUNTER_SIMPLE_H__ */ + diff --git a/neo/prey/Prey/ai_inspector.cpp b/neo/prey/Prey/ai_inspector.cpp new file mode 100644 index 00000000..6b9b073a --- /dev/null +++ b/neo/prey/Prey/ai_inspector.cpp @@ -0,0 +1,136 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_CheckReactions( "", "e" ); + +CLASS_DECLARATION( hhMonsterAI, hhAIInspector ) + EVENT( EV_PostSpawn, hhAIInspector::Event_PostSpawn ) + EVENT( MA_CheckReactions, hhAIInspector::Event_CheckReactions ) +END_CLASS + +void hhAIInspector::Spawn() { +} + +void hhAIInspector::Event_PostSpawn() { + const function_t* func = GetScriptFunction( "state_InspectorIdle" ); + if( !func ) { + gameLocal.Warning( "unable to find state_InspectorIdle" ); + return; + } + SetState( func ); +} + +void hhAIInspector::CheckReactions( idEntity* entity ) { + PostEventMS( &MA_CheckReactions, 1000, entity ); +} + +void hhAIInspector::Event_CheckReactions( idEntity* entity ) { + checkReaction = entity; + targetReaction.entity = entity; + targetReaction.reactionIndex = 0; + if( !targetReaction.GetReaction() ) { + targetReaction.reactionIndex = -1; + gameLocal.Warning( "unable to properly use requested reaction... possibly no reactions on this entity" ); + return; + } + + const function_t* func = GetScriptFunction( "state_InspectorReactions" ); + if( !func ) { + gameLocal.Warning( "unable to find checkreaction state on inspector..." ); + return; + } + SetState( func ); +} + +void hhAIInspector::RestartInspector( const char* inspectClass, idEntity* newReaction, idPlayer* starter ) { + float yaw; + idVec3 org; + idDict spawnDict; + idEntity* react_scout; + idEntity* use_reaction = NULL; + +//Find our dictionary to use + const idDict* edict = gameLocal.FindEntityDefDict( inspectClass, false ); + if( !edict ) { + gameLocal.Printf( "Unable to find entitfydef '%s'", inspectClass ); + return; + } + spawnDict = *edict; //copy the values to an editable dict + +//Setup values for position/orientation + yaw = starter->viewAngles.yaw; + org = starter->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 180 + idVec3( 0, 0, 1 ); + +// Set dictionary values... + spawnDict.Set( "angle", va("%f", yaw + 180) ); + spawnDict.Set( "origin", org.ToString() ); + spawnDict.Set( "spawnClass", "hhAIInspector" ); + +// Perform actual spawn + react_scout = static_cast(gameLocal.SpawnEntityType(hhAIInspector::Type, &spawnDict)); + if( !react_scout ) { + gameLocal.Printf( "failed to spawn inspector monster..." ); + return; + } + + use_reaction = newReaction; + + if( gameLocal.inspector != NULL ) { +//save our old use_reaction if we haven't specified one + if( !use_reaction && gameLocal.inspector.IsValid() && gameLocal.inspector->checkReaction.IsValid() ) { + use_reaction = gameLocal.inspector->checkReaction.GetEntity(); + } +// Remove our old inspector + gameLocal.inspector->PostEventMS( &EV_Remove, 0 ); + } + gameLocal.inspector = react_scout; + if( use_reaction ) { + gameLocal.inspector->CheckReactions( use_reaction ); + } +} + +void hhAIInspector::RestartInspector( idEntity* newReaction, idPlayer* starter ) { + idEntity* react_scout; + float yaw; + idVec3 org; + + yaw = starter->viewAngles.yaw; + org = starter->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 180 + idVec3( 0, 0, 1 ); + + idDict dict = gameLocal.inspector.GetEntity()->spawnArgs; + + dict.Set( "angle", va("%f", yaw + 180) ); + dict.Set( "origin", org.ToString() ); + react_scout = static_cast(gameLocal.SpawnEntityType(hhAIInspector::Type, &dict)); + if( !react_scout ) { + gameLocal.Printf( "failed to spawn inspector monster..." ); + return; + } + + if( gameLocal.inspector != NULL ) { + gameLocal.inspector->PostEventMS( &EV_Remove, 0 ); + } + gameLocal.inspector = react_scout; + gameLocal.inspector->CheckReactions( newReaction ); +} + +/* +===================== +hhAIInspector::Save +===================== +*/ +void hhAIInspector::Save( idSaveGame *savefile ) const { + checkReaction.Save( savefile ); +} + +/* +===================== +hhAIInspector::Restore +===================== +*/ +void hhAIInspector::Restore( idRestoreGame *savefile ) { + checkReaction.Restore( savefile ); +} + diff --git a/neo/prey/Prey/ai_inspector.h b/neo/prey/Prey/ai_inspector.h new file mode 100644 index 00000000..40dbe79b --- /dev/null +++ b/neo/prey/Prey/ai_inspector.h @@ -0,0 +1,28 @@ +#ifndef __PREY_AI_INSPECTOR_H__ +#define __PREY_AI_INSPECTOR_H__ + +class hhAIInspector : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhAIInspector); + +public: + void Spawn(); + void CheckReactions( idEntity* entity ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + static void RestartInspector( const char* inspectClass, idEntity* newReaction, idPlayer* starter ); + static void RestartInspector( idEntity* newReaction, idPlayer* starter ); + +public: +// Events. + void Event_PostSpawn(); + void Event_CheckReactions( idEntity* entity ); + +protected: + idEntityPtr checkReaction; + +}; + +#endif diff --git a/neo/prey/Prey/ai_jetpack_harvester_simple.cpp b/neo/prey/Prey/ai_jetpack_harvester_simple.cpp new file mode 100644 index 00000000..cde47fcb --- /dev/null +++ b/neo/prey/Prey/ai_jetpack_harvester_simple.cpp @@ -0,0 +1,662 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef AI_RemoveJetFx("removeJetFx"); +const idEventDef AI_CreateJetFx("createJetFx"); +const idEventDef AI_LaunchMine("launchMine", "d"); +const idEventDef MA_IsEnemySniping("isEnemySniping", NULL, 'd'); +const idEventDef MA_NumMines("numMines", NULL, 'f'); +const idEventDef MA_DodgeProjectile("", "e"); +const idEventDef MA_AppendHoverFx( "", "e" ); + +CLASS_DECLARATION( hhMonsterAI, hhJetpackHarvesterSimple ) + EVENT( AI_RemoveJetFx, hhJetpackHarvesterSimple::Event_RemoveJetFx ) + EVENT( EV_Activate, hhJetpackHarvesterSimple::Event_Activate) + EVENT( AI_CreateJetFx, hhJetpackHarvesterSimple::Event_CreateJetFx ) + EVENT( AI_LaunchMine, hhJetpackHarvesterSimple::Event_LaunchMine ) + EVENT( EV_Broadcast_AppendFxToList, hhJetpackHarvesterSimple::Event_AppendToThrusterList ) + EVENT( MA_OnProjectileLaunch, hhJetpackHarvesterSimple::Event_OnProjectileLaunch ) + EVENT( MA_DodgeProjectile, hhJetpackHarvesterSimple::Event_DodgeProjectile ) + EVENT( MA_IsEnemySniping, hhJetpackHarvesterSimple::Event_IsEnemySniping ) + EVENT( MA_NumMines, hhJetpackHarvesterSimple::Event_NumMines ) + EVENT( MA_AppendHoverFx, hhJetpackHarvesterSimple::Event_AppendHoverFx ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +hhJetpackHarvesterSimple::~hhJetpackHarvesterSimple() { + for ( int i=0;iGetPhysics()->GetAxis(); + collision.endpos = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.point = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + mineList[i]->Explode( &collision, idVec3(0,0,0), 0 ); + } + } +} + +void hhJetpackHarvesterSimple::Spawn(void) { + lastAntiProjectileAttack = gameLocal.GetTime(); + for(int i=0;iGetJointWorldTransform( bones[i], bonePos, boneAxis ); + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( bones[i] ); + fxInfo.Toggle(); + fxInfo.RemoveWhenDone( false ); + + if( fxThrusters[ThrustType_Idle][i] != NULL ) { + gameLocal.Warning("Jetpack harvester already had flamejet made."); + } + + BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList ); + } + } + + psystem = spawnArgs.GetString( "fx_thruster_hover" ); + if ( psystem && *psystem ) { + for(int i=0;i* parmList ) { + if( !parmList || parmList->Num() != 2 ) { + return; + } + + idVec3 jointPos; + idMat3 jointAxis; + const char* projectileDefName = (*parmList)[0].c_str(); + const char* jointName = (*parmList)[1].c_str(); + + if( !projectileDefName || !projectileDefName[0] ) { + return; + } + + if( !GetJointWorldTransform(jointName, jointPos, jointAxis) ) { + return; + } + + hhHarvesterMine* mine = static_cast( gameLocal.SpawnObject(projectileDefName) ); + mine->Create( this, jointPos, jointAxis ); + mine->Launch( jointPos, jointAxis, physicsObj.GetPushedLinearVelocity() ); +} + +void hhJetpackHarvesterSimple::Hide( void ) { + hhMonsterAI::Hide(); + + for(int i=0;iNozzle(FALSE); + } + if(fxThrusters[ThrustType_Hover][i].GetEntity()) { + fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE); + } + } +} + +void hhJetpackHarvesterSimple::Show( void ) { + hhMonsterAI::Show(); + + for(int i=0;iNozzle(TRUE); + } + if(fxThrusters[ThrustType_Hover][i].GetEntity()) { + fxThrusters[ThrustType_Hover][i]->Nozzle(FALSE); + } + } +} + +idProjectile *hhJetpackHarvesterSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = desiredProjectileDef->GetFloat( "projectile_spread", "0" ); + num_projectiles = desiredProjectileDef->GetInt( "num_projectiles", "1" ); + idVec3 launch_velocity = vec3_zero; + if ( desiredProjectileDef->GetFloat( "launch_y", "0" ) > 0.0f ) { + if ( gameLocal.random.RandomFloat() < 0.5 ) { + launch_velocity.y = -desiredProjectileDef->GetFloat( "launch_y", "0" ); + } else { + launch_velocity.y = desiredProjectileDef->GetFloat( "launch_y", "0" ); + } + launch_velocity *= viewAxis; + } + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, launch_velocity ); + projectile = NULL; + if ( projectileDef->GetBool( "mine", "0" ) ) { + idEntityPtr &entityPtr = mineList.Alloc(); + entityPtr = lastProjectile; + } + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhJetpackHarvesterSimple::AnimMove( void ) { + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + physicsObj.UseFlyMove( false ); + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + delta *= spawnArgs.GetFloat( "fly_scale", "1.0" ); + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + if ( ai_debugMove.GetBool() ) { + // HUMANHEAD JRM - so we can see if grav is on or off + if(disableGravity) { + gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 ); + } else { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhJetpackHarvesterSimple::Event_NumMines() { + //we only want valid mines, so remove dead ones + for ( int i=0;ihealth <= 0 ) { + mineList.RemoveIndex(i); + } + } + idThread::ReturnFloat( float(mineList.Num()) ); +} + +void hhJetpackHarvesterSimple::Event_DodgeProjectile( hhProjectile *proj ) { + if ( !proj || !enemy.IsValid() ) { + return; + } + float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" ); + float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" ); + + float dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() ); + + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance ); + } + if ( gameLocal.random.RandomFloat() > dodgeChance ) { + return; + } + + idVec3 fw = viewAxis[0]; + idVec3 projFw = proj->GetAxis()[0]; + if(proj->GetOwner()) + projFw = proj->GetOwner()->GetAxis()[0]; + float dot = fw * projFw; + if(dot > -.7f) + return; + const function_t *newstate = NULL; + + //determine which side to dodge to + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + idVec3 povLeft, povUp; + povToTarget.OrthogonalBasis(povLeft, povUp); + povLeft.Normalize(); + idVec3 projVel = proj->GetPhysics()->GetLinearVelocity(); + projVel.Normalize(); + dot = povLeft * projVel; + if ( dot < 0 ) { + newstate = GetScriptFunction( "state_DodgeRight" ); + } else { + newstate = GetScriptFunction( "state_DodgeLeft" ); + } + if ( newstate ) { + lastAntiProjectileAttack = gameLocal.GetTime(); + SetState( newstate ); + UpdateScript(); + } +} + +void hhJetpackHarvesterSimple::Event_OnProjectileLaunch( hhProjectile *proj ) { + if ( !proj || !spawnArgs.GetBool( "can_dodge", "1" ) || health <= 0 ) { + return; + } + if ( gameLocal.GetTime() - lastAntiProjectileAttack < 1000 * int(spawnArgs.GetFloat( "dodge_freq", "2.0" )) ) { + return; + } + if( proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)) ) { + return; // The person who launched this projectile wasn't someone to worry about + } + if ( !enemy.IsValid() ) { + return; + } + PostEventSec( &MA_DodgeProjectile, spawnArgs.GetFloat( "dodge_delay", "0.5" ), proj ); +} + +bool hhJetpackHarvesterSimple::Collide( const trace_t &collision, const idVec3 &velocity ) { + if ( af.IsActive() && !IsHidden() && !freezeDamage && !specialDamage ) { + const char *fx = spawnArgs.GetString("fx_death"); + if ( fx && *fx ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone(true); + BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo); + Hide(); + PostEventMS(&EV_Remove, 1000); + } + } + + return false; +} + +void hhJetpackHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (!frozen) { + // Being tractored can mess up predeath + fl.canBeTractored = false; + } + HandleNoGore(); + if ( AI_DEAD ) { + AI_PAIN = true; + AI_DAMAGE = true; + return; + } + + //stop rocket fx and sounds + StopSound( SND_CHANNEL_BODY3, false ); + for(int i=0;iGetGravityNormal() * 800, MASK_MONSTERSOLID, this); + if( TraceInfo.fraction < 1.0f ) { + if ( HasPathTo( TraceInfo.endpos ) ) { + Event_SetMoveType( MOVETYPE_ANIM ); + health += 30; + state = GetScriptFunction( "state_StartPreDeath" ); + SetState( state ); + SetWaitState( "" ); + return; + } + } + } + + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + //explode creature's mines upon death + for ( int i=0;iGetPhysics()->GetAxis(); + collision.endpos = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.point = mineList[i]->GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + mineList[i]->Explode( &collision, idVec3(0,0,0), 0 ); + } + } +} + +void hhJetpackHarvesterSimple::Event_IsEnemySniping(void) { + idThread::ReturnInt( false ); +} + +/* +===================== +hhJetpackHarvesterSimple::Save +===================== +*/ +void hhJetpackHarvesterSimple::Save( idSaveGame *savefile ) const { + int i; + for ( i = 0; i < ThrustSide_Total; i++ ) { + for ( int j = 0; j < ThrustType_Total; j++ ) { + fxThrusters[i][j].Save( savefile ); + } + } + + savefile->WriteInt( lastAntiProjectileAttack ); + savefile->WriteBool( allowPreDeath ); + + int num = mineList.Num(); + savefile->WriteInt( num ); + savefile->WriteBool( specialDamage ); + for ( i = 0; i < num; i++ ) { + mineList[i].Save( savefile ); + } +} + +/* +===================== +hhJetpackHarvesterSimple::Restore +===================== +*/ +void hhJetpackHarvesterSimple::Restore( idRestoreGame *savefile ) { + int i; + for ( i = 0; i < ThrustSide_Total; i++ ) { + for ( int j = 0; j < ThrustType_Total; j++ ) { + fxThrusters[i][j].Restore( savefile ); + } + } + + savefile->ReadInt( lastAntiProjectileAttack ); + savefile->ReadBool( allowPreDeath ); + + int num; + savefile->ReadInt( num ); + savefile->ReadBool( specialDamage ); + mineList.SetNum( num ); + for ( i = 0; i < num; i++ ) { + mineList[i].Restore( savefile ); + } +} + +void hhJetpackHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( !AI_DEAD ) { + if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { + freezeDamage = true; + allowPreDeath = false; + } else { + freezeDamage = false; + } + if ( damageDef && damageDef->GetBool( "no_special_death" ) ) { + specialDamage = true; + allowPreDeath = false; + } + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_jetpack_harvester_simple.h b/neo/prey/Prey/ai_jetpack_harvester_simple.h new file mode 100644 index 00000000..44089bcb --- /dev/null +++ b/neo/prey/Prey/ai_jetpack_harvester_simple.h @@ -0,0 +1,66 @@ + +#ifndef __PREY_AI_JETPACK_HARVESTER_SIMPLE_H__ +#define __PREY_AI_JETPACK_HARVESTER_SIMPLE_H__ + + +class hhJetpackHarvesterSimple : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhJetpackHarvesterSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_AppendHoverFx( hhEntityFx* fx ) {}; + void Event_AppendToThrusterList( hhEntityFx* fx ) {}; + void Event_RemoveJetFx(void) {}; + void Event_CreateJetFx(void) {}; + void Event_LaunchMine( const idList* parmList ) {}; + void Event_OnProjectileLaunch( hhProjectile *proj ) {}; + void Event_DodgeProjectile( hhProjectile *proj ) {}; + void Event_IsEnemySniping(void) {}; + void Event_NumMines(void) {}; +#else +protected: + enum ThrustSide + { + ThrustSide_Left, + ThrustSide_Right, + ThrustSide_Total + }; + + enum ThrustType + { + ThrustType_Idle = 0, + ThrustType_Hover, + ThrustType_Total + }; + + ~hhJetpackHarvesterSimple(); + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void AnimMove( void ); + void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + idProjectile* LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void Event_AppendHoverFx( hhEntityFx* fx ); + void Event_AppendToThrusterList( hhEntityFx* fx ); + void Event_RemoveJetFx(void); + void Event_CreateJetFx(void); + void Event_LaunchMine( const idList* parmList ); + void Event_OnProjectileLaunch( hhProjectile *proj ); + void Event_DodgeProjectile( hhProjectile *proj ); + void Event_IsEnemySniping(void); + void Event_NumMines(void); + virtual void Hide( void ); + virtual void Show( void ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + idEntityPtr fxThrusters[ThrustType_Total][ThrustSide_Total]; + int lastAntiProjectileAttack; + bool allowPreDeath; + idList< idEntityPtr > mineList; + bool freezeDamage; + bool specialDamage; +#endif +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_keeper_simple.cpp b/neo/prey/Prey/ai_keeper_simple.cpp new file mode 100644 index 00000000..215eddaa --- /dev/null +++ b/neo/prey/Prey/ai_keeper_simple.cpp @@ -0,0 +1,694 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_KeeperStartBlast("keeper_StartBlast", "e"); +const idEventDef MA_KeeperEndBlast("keeper_EndBlast", NULL); +const idEventDef MA_KeeperUpdateTelepathicThrow("keeper_UpdateTelepathicThrow", NULL, NULL); +const idEventDef MA_KeeperTelepathicThrow("keeper_TelepathicThrow", NULL, NULL); +const idEventDef MA_KeeperStartTeleport("keeper_StartTeleport", NULL); +const idEventDef MA_KeeperEndTeleport("keeper_EndTeleport", NULL); +const idEventDef MA_KeeperTeleportExit("keeper_TeleportExit", NULL); +const idEventDef MA_KeeperCreatePortal("keeper_createPortal", NULL); +const idEventDef MA_KeeperEnableShield("keeper_enableShield", NULL); +const idEventDef MA_KeeperDisableShield("keeper_disableShield", NULL); +const idEventDef MA_KeeperAssignShieldFx( "", "e" ); +const idEventDef MA_KeeperTrigger("keeper_Trigger", NULL, NULL); +const idEventDef MA_KeeperTeleportEnter("keeper_TeleportEnter", NULL); +const idEventDef MA_KeeperStartHeadFx("keeper_StartHeadFx"); +const idEventDef MA_KeeperEndHeadFx("keeper_EndHeadFx"); +const idEventDef MA_KeeperAssignHeadFx( "", "e" ); +const idEventDef MA_KeeperGetThrowEntity( "keeper_GetThrowEntity", NULL, 'e' ); +const idEventDef MA_KeeperGetTriggerEntity( "keeper_GetTriggerEntity", NULL, 'e' ); +const idEventDef MA_KeeperTriggerEntity( "keeper_TriggerEntity", "e" ); +const idEventDef MA_KeeperThrowEntity( "keeper_ThrowEntity", "e" ); +const idEventDef MA_KeeperForceDisableShield("keeper_forceDisableShield", NULL); + +CLASS_DECLARATION( hhMonsterAI, hhKeeperSimple ) + EVENT( MA_KeeperStartBlast, hhKeeperSimple::Event_StartBlast ) + EVENT( MA_KeeperEndBlast, hhKeeperSimple::Event_EndBlast ) + EVENT( MA_KeeperUpdateTelepathicThrow, hhKeeperSimple::Event_UpdateTelepathicThrow ) + EVENT( MA_KeeperTelepathicThrow, hhKeeperSimple::Event_TelepathicThrow ) + EVENT( MA_KeeperStartTeleport, hhKeeperSimple::Event_StartTeleport ) + EVENT( MA_KeeperEndTeleport, hhKeeperSimple::Event_EndTeleport ) + EVENT( MA_KeeperTeleportExit, hhKeeperSimple::Event_TeleportExit ) + EVENT( MA_KeeperCreatePortal, hhKeeperSimple::Event_CreatePortal ) + EVENT( MA_KeeperEnableShield, hhKeeperSimple::Event_EnableShield ) + EVENT( MA_KeeperDisableShield, hhKeeperSimple::Event_DisableShield ) + EVENT( MA_KeeperAssignShieldFx, hhKeeperSimple::Event_AssignShieldFx ) + EVENT( MA_KeeperTrigger, hhKeeperSimple::Event_KeeperTrigger ) + EVENT( MA_KeeperTeleportEnter, hhKeeperSimple::Event_TeleportEnter ) + EVENT( MA_KeeperStartHeadFx, hhKeeperSimple::Event_StartHeadFx ) + EVENT( MA_KeeperEndHeadFx, hhKeeperSimple::Event_EndHeadFx ) + EVENT( MA_KeeperAssignHeadFx, hhKeeperSimple::Event_AssignHeadFx ) + EVENT( MA_KeeperTriggerEntity, hhKeeperSimple::Event_TriggerEntity ) + EVENT( MA_KeeperThrowEntity, hhKeeperSimple::Event_ThrowEntity ) + EVENT( MA_KeeperGetTriggerEntity, hhKeeperSimple::Event_GetTriggerEntity ) + EVENT( MA_KeeperGetThrowEntity, hhKeeperSimple::Event_GetThrowEntity ) + EVENT( MA_KeeperForceDisableShield, hhKeeperSimple::Event_ForceDisableShield ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +hhKeeperSimple::hhKeeperSimple() { + shieldAlpha = 0.0f; +} + +hhKeeperSimple::~hhKeeperSimple() { + idEntity *ent = NULL; + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } else { + return; + } + + if ( ent && ent->IsType(hhMoveable::Type)) { + hhMoveable *throwMoveable = static_cast(ent); + if ( throwMoveable ) { + throwMoveable->Event_Unhover(); + } + } +} + +void hhKeeperSimple::Spawn( void ) { + beamAttack = idEntityPtr ( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamAttack" ) ) ); + if(beamAttack.IsValid()) { + beamAttack->MoveToJoint( this, spawnArgs.GetString("bone_beamAttack")); + beamAttack->BindToJoint( this, spawnArgs.GetString("bone_beamAttack"), false ); + beamAttack->Activate(FALSE); + } + + beamTelepathic = idEntityPtr (hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "beamTelepathic" ) ) ); + if(beamTelepathic.IsValid()) { + beamTelepathic->MoveToJoint( this, spawnArgs.GetString("bone_beamTelepathic")); + beamTelepathic->BindToJoint( this, spawnArgs.GetString("bone_beamTelepathic"), false ); + beamTelepathic->Activate(FALSE); + beamTelepathic->Hide(); + } + if ( spawnArgs.GetInt( "always_shield", "0" ) && !spawnArgs.GetBool( "hide", "0" ) ) { + Event_EnableShield(); + } + nextShieldImpact = gameLocal.time; + bThrowing = false; +} + +void hhKeeperSimple::Think() { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + idEntity *ent = NULL; + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } + + // Draw beam from our brain to the object we are controlling + if ( beamTelepathic.IsValid() ) { + if( bThrowing && ent && !beamTelepathic->IsHidden() && beamTelepathic->IsActivated() ) { + beamTelepathic->SetTargetLocation(ent->GetOrigin()); + } else { + beamTelepathic->Activate( false ); + beamTelepathic->Hide(); + } + } + + if ( shieldFx.IsValid() ) { + if ( AI_SHIELD ) { + //fade on shield + shieldAlpha += spawnArgs.GetFloat( "shield_delta", "0.05" ); + if ( shieldAlpha > 1.0 ) { + shieldAlpha = 1.0f; + } + } else { + //fade off shield + shieldAlpha -= spawnArgs.GetFloat( "shield_delta", "0.05" ); + if ( shieldAlpha < 0.0 ) { + shieldAlpha = 0.0f; + } + } + shieldFx->SetParticleShaderParm( SHADERPARM_TIME_OF_DEATH, shieldAlpha ); + } + + hhMonsterAI::Think(); + + if ( physicsObj.HasGroundContacts() && !physicsObj.HadGroundContacts() ) { + AI_LANDED = true; + } +} + +void hhKeeperSimple::Event_StartBlast( idEntity *ent) { + if( beamAttack.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.SetEntity( ent ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_portalstart"), ent->GetOrigin() + (spawnArgs.GetVector( "portal_start_offset" ) * ent->GetAxis()), ent->GetAxis(), &fxInfo ); + beamAttack->SetTargetLocation( ent->GetOrigin() + ent->GetAxis() * spawnArgs.GetVector( "telepathic_beam_offset" ) ); + beamAttack->Activate(TRUE); + } +} + +void hhKeeperSimple::Event_EndBlast() { + if( beamAttack.IsValid() ) { + beamAttack->Activate(FALSE); + } +} + +void hhKeeperSimple::Event_UpdateTelepathicThrow() { + idEntity *ent = NULL; + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } else { + return; + } + if ( !ent || !ent->IsType(hhMoveable::Type) ) { + return; + } + bThrowing = true; + if ( beamTelepathic.IsValid() ) { + if ( beamTelepathic->IsHidden() ) { + beamTelepathic->Show(); + } + beamTelepathic->Activate( true ); + } + + hhMoveable *throwMoveable = static_cast(ent); + idVec3 hoverToPos; + + if(GetEnemy()) { + UpdateEnemyPosition(); + idVec3 toEnemy = GetLastEnemyPos() - GetOrigin(); + float dist = toEnemy.Normalize(); + hoverToPos = GetOrigin() + toEnemy * dist * 0.25f; + hoverToPos.z = GetOrigin().z + EyeHeight() + 200.0f; + } + else { + hoverToPos = throwMoveable->GetOrigin() + idVec3(0.0f, 0.0f, 128.0f); + } + throwMoveable->AllowImpact( true ); + throwMoveable->Event_HoverTo(hoverToPos); + throwMoveable->GetPhysics()->SetAngularVelocity(idVec3(10.0f, 10.0f, 10.0f)); + // HUMANHEAD mdl: Beef up the damage on movables thrown by the keeper + throwMoveable->SetDamageDef( spawnArgs.GetString( "moveableDamageDef", "damage_movable20" ) ); + PostEventMS(&MA_KeeperUpdateTelepathicThrow, 100); +} + +void hhKeeperSimple::Event_TelepathicThrow() { + idEntity *ent = NULL; + bThrowing = false; + if ( beamTelepathic.IsValid() ) { + beamTelepathic->Hide(); + } + if ( throwEntity.IsValid() ) { + ent = throwEntity.GetEntity(); + } else if ( targetReaction.entity.IsValid() ) { + ent = targetReaction.entity.GetEntity(); + } else { + return; + } + + // Start the object hovering + if ( !ent || !ent->IsType(hhMoveable::Type) ) { + return; + } + + targetReaction.entity.Clear(); + CancelEvents( &MA_KeeperUpdateTelepathicThrow ); + hhMoveable *throwMoveable = static_cast(ent); + if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) { + throwMoveable->Event_Unhover(); + } + + // Throw it at an enemy if we have one + if(GetEnemy()) { + idVec3 toEnemy = GetEnemy()->GetAimPosition() - throwMoveable->GetOrigin(); + toEnemy.Normalize(); + throwMoveable->GetPhysics()->SetLinearVelocity(toEnemy * spawnArgs.GetFloat("telepathic_throw_velocity", "2000")); + } +} + +void hhKeeperSimple::Event_TeleportExit() { + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_TeleportExit" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Event_TeleportEnter() { + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_TeleportEnter" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Event_StartTeleport() { + Hide(); +} + +void hhKeeperSimple::Event_EndTeleport() { + idVec3 newOrig; + idEntity *ent; + idList nodeList; + float bestScore = -1; + float bestNodeIndex = -1; + + // Find the best position to teleport to + // Find where we want to teleport to + idStr randomNode = spawnArgs.RandomPrefix( "teleport_info", gameLocal.random ); + ent = gameLocal.FindEntity( randomNode ); + if ( ent ) { + newOrig = ent->GetOrigin(); + } else { + newOrig = GetOrigin(); + gameLocal.Warning( "%s doesn't have any teleport infos", name.c_str() ); + } + + //teleport and face enemy + SetOrigin( newOrig ); + if( GetEnemy() ) { + TurnToward(GetEnemy()->GetOrigin()); + current_yaw = ideal_yaw; + Turn(); + } +} + +float hhKeeperSimple::GetTeleportDestRating(const idVec3 &pos) { + // Make sure this spot doesn't contain something else + idEntity *entList[MAX_GENTITIES]; + idBounds myNewBnds = this->GetPhysics()->GetBounds(); + myNewBnds = myNewBnds.Translate(pos); + myNewBnds.ExpandSelf(24.0f); + int numHit = gameLocal.clip.EntitiesTouchingBounds(myNewBnds, -1, entList, MAX_GENTITIES); + if(numHit > 0) { + for(int i=0;iIsHidden() && (entList[i]->IsType(hhMoveable::Type) || entList[i]->IsType(idActor::Type))) { + return -1; + } + } + } + + float w = gameLocal.random.RandomFloat(); + + return idMath::ClampFloat(-1.0f, 1.0f, w); +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhKeeperSimple::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_LANDED ); + LinkScriptVariable( AI_SHIELD ); +} + +void hhKeeperSimple::Event_CreatePortal (void) { + static const char * passPrefix = "portal_"; + const char * portalDef; + idEntity * portal; + idDict portalArgs; + idList xferKeys; + idList xferValues; + + const idKeyValue *buddyKV = spawnArgs.FindKey( "portal_buddy" ); + if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) { + // Case of a valid portal_buddy key, make a real portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else if ( spawnArgs.FindKey( "portal_cameraTarget" ) ) { // The monster portal has a camera target, so use a real portal instead of the fake portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else { + portalDef = spawnArgs.GetString( "def_fakeportal" ); + } + + if ( !portalDef || !portalDef[0] ) { + return; + } + + // Set the origin of the portal to us. + portalArgs.SetVector( "origin", GetOrigin() ); + portalArgs.Set( "rotation", spawnArgs.GetString( "rotation" ) ); + portalArgs.Set( "remove_on_close", "1" ); + + // Pass along all 'portal_' keys to the portal's spawnArgs; + hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues ); + for ( int i = 0; i < xferValues.Num(); ++i ) { + xferKeys[ i ].StripLeadingOnce( passPrefix ); + portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + } + + // Spawn the portal + portal = gameLocal.SpawnObject( portalDef, &portalArgs ); + if ( !portal ) { + return; + } + + // Move the portal up some pre determinted amt, since its origin is in the middle of it + trace_t tr; + gameLocal.clip.TracePoint(tr, GetOrigin(), GetOrigin() + idVec3(0,0,-200), MASK_MONSTERSOLID, this); + if ( tr.fraction < 1.0f ) { + portal->GetPhysics()->SetOrigin( tr.c.point + idVec3( 0,0,2 ) ); + } else { + float offset = spawnArgs.GetFloat( "offset_portal", 0 ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) ); + } + + // Update the camera stuff + portal->ProcessEvent( &EV_UpdateCameraTarget ); + + // Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal + //? Should we always pass in the player? + portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() ); +} + +void hhKeeperSimple::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + if ( beamAttack.IsValid() ) { + SAFE_REMOVE( beamAttack ); + } + if( beamTelepathic.IsValid() ) { + SAFE_REMOVE( beamTelepathic ); + } + hhMoveable *throwMoveable = static_cast(targetReaction.entity.GetEntity()); + if ( throwMoveable && throwMoveable->IsType( hhMoveable::Type ) ) { + targetReaction.entity.Clear(); + throwMoveable->Event_Unhover(); + } + if ( renderEntity.customSkin == NULL ) { + SetSkinByName( "skins/monsters/keeper_nolegs" ); + } +} + +void hhKeeperSimple::Event_EnableShield(void) { + if ( spawnArgs.GetInt( "never_shield", "0" ) ) { + return; + } + fl.applyDamageEffects = false; + AI_SHIELD = true; + if ( shieldFx.IsValid() ) { + SAFE_REMOVE( shieldFx ); + } + hhFxInfo fxInfo; + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( "head" ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_shield"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignShieldFx ); +} + +void hhKeeperSimple::Event_ForceDisableShield(void) { + fl.applyDamageEffects = true; + AI_SHIELD = false; +} + +void hhKeeperSimple::Event_DisableShield(void) { + if ( spawnArgs.GetInt( "always_shield", "0" ) ) { + return; + } + fl.applyDamageEffects = true; + AI_SHIELD = false; +} + +void hhKeeperSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int loc ) { + if ( AI_SHIELD ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool( "spirit_damage" ) ) { + hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc); + } + if ( inflictor ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + if ( gameLocal.time >= nextShieldImpact ) { + nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); + BroadcastFxInfoPrefixed( "fx_shield_impact", inflictor->GetOrigin() + GetAxis()*idVec3(50,0,0), mat3_identity, &fxInfo ); + } + StartSound( "snd_shield_impact", SND_CHANNEL_BODY ); + if ( inflictor->IsType( idProjectile::Type ) ) { + inflictor->PostEventMS( &EV_Remove, 0 ); + } + } + } else { + hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, loc); + } +} + +void hhKeeperSimple::Event_AssignShieldFx( hhEntityFx* fx ) { + shieldFx = fx; +} + +void hhKeeperSimple::Event_StartHeadFx() { + hhFxInfo fxInfo; + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( spawnArgs.GetString( "bone_telepathic_fx", "head" ) ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_telepathic"), GetOrigin(), mat3_identity, &fxInfo, &MA_KeeperAssignHeadFx ); +} + +void hhKeeperSimple::Event_EndHeadFx() { + SAFE_REMOVE( headFx ); +} + +void hhKeeperSimple::Event_AssignHeadFx( hhEntityFx* fx ) { + headFx = fx; +} + +void hhKeeperSimple::Event_KeeperTrigger() { + if ( targetReaction.entity.IsValid() ) { + targetReaction.entity->ProcessEvent( &EV_Activate, this ); + hhReaction *reaction = targetReaction.GetReaction(); + if (reaction) { + reaction->active = false; + } + } +} + +idProjectile *hhKeeperSimple::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs. + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" ); + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + GetAimDir( muzzle, target, this, dir ); + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //dir = axis[ 0 ] + axis[ 2 ] * ( 0 * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir = axis[ 0 ] - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle, axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhKeeperSimple::Event_GetTriggerEntity() { + idThread::ReturnEntity( triggerEntity.GetEntity() ); +} + +void hhKeeperSimple::Event_TriggerEntity( idEntity *ent ) { + if ( !ent ) { + return; + } + triggerEntity = ent; + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_TriggerEntity" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Event_GetThrowEntity() { + idThread::ReturnEntity( throwEntity.GetEntity() ); +} + +void hhKeeperSimple::Event_ThrowEntity( idEntity *ent ) { + if ( !ent || !ent->IsType( idMoveable::Type ) ) { + return; + } + throwEntity = ent; + const function_t *newstate = NULL; + newstate = GetScriptFunction( "state_ThrowEntity" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +void hhKeeperSimple::Hide() { + Event_DisableShield(); + hhMonsterAI::Hide(); +} + +void hhKeeperSimple::Show() { + hhMonsterAI::Show(); + if ( spawnArgs.GetInt( "always_shield", "0" ) ) { + Event_EnableShield(); + } +} + +void hhKeeperSimple::HideNoDormant() { + //overridden to hide shield fx + if ( shieldFx.IsValid() ) { + shieldFx->Hide(); + } + idAI::Hide(); +} + +/* +===================== +hhKeeperSimple::Save +===================== +*/ +void hhKeeperSimple::Save( idSaveGame *savefile ) const { + beamTelepathic.Save( savefile ); + beamAttack.Save( savefile ); + shieldFx.Save( savefile ); + headFx.Save( savefile ); + triggerEntity.Save( savefile ); + throwEntity.Save( savefile ); + savefile->WriteInt( nextShieldImpact ); + savefile->WriteBool( bThrowing ); +} + +/* +===================== +hhKeeperSimple::Restore +===================== +*/ +void hhKeeperSimple::Restore( idRestoreGame *savefile ) { + beamTelepathic.Restore( savefile ); + beamAttack.Restore( savefile ); + shieldFx.Restore( savefile ); + headFx.Restore( savefile ); + triggerEntity.Restore( savefile ); + throwEntity.Restore( savefile ); + savefile->ReadInt( nextShieldImpact ); + savefile->ReadBool( bThrowing ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_keeper_simple.h b/neo/prey/Prey/ai_keeper_simple.h new file mode 100644 index 00000000..85d7d82a --- /dev/null +++ b/neo/prey/Prey/ai_keeper_simple.h @@ -0,0 +1,98 @@ + +#ifndef __PREY_AI_KEEPER_SIMPLE_H__ +#define __PREY_AI_KEEPER_SIMPLE_H__ + +class hhKeeperSimple : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhKeeperSimple); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_GetTriggerEntity() {}; + void Event_TriggerEntity( idEntity *ent ) {}; + void Event_GetThrowEntity() {}; + void Event_ThrowEntity( idEntity *ent ) {}; + void Event_StartBlast( idEntity *ent ) {}; + void Event_EndBlast() {}; + void Event_BeginTelepathicThrow(idEntity *throwMe) {}; + void Event_UpdateTelepathicThrow() {}; + void Event_TelepathicThrow() {}; + void Event_StartTeleport() {}; + void Event_EndTeleport() {}; + void Event_TeleportExit() {}; + void Event_TeleportEnter() {}; + void Event_CreatePortal() {}; + void Event_EnableShield() {}; + void Event_DisableShield() {}; + void Event_ForceDisableShield() {}; + void Event_AssignShieldFx( hhEntityFx* fx ) {}; + void Event_StartHeadFx() {}; + void Event_EndHeadFx() {}; + void Event_AssignHeadFx( hhEntityFx* fx ) {}; + void Event_KeeperTrigger() {}; +#else + + + hhKeeperSimple(); + ~hhKeeperSimple(); + void Spawn( void ); + virtual void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void LinkScriptVariables(); + virtual 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 ); + idProjectile* LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void Hide(); + void Show(); + void HideNoDormant(); + idScriptBool AI_LANDED; + idScriptBool AI_SHIELD; +protected: + void Event_GetTriggerEntity(); + void Event_TriggerEntity( idEntity *ent ); + void Event_GetThrowEntity(); + void Event_ThrowEntity( idEntity *ent ); + + // Attack blast + void Event_StartBlast( idEntity *ent ); + void Event_EndBlast(); + + // Telepathic Throwing + void Event_BeginTelepathicThrow(idEntity *throwMe); + void Event_UpdateTelepathicThrow(); + void Event_TelepathicThrow(); + + // Teleporting + void Event_StartTeleport(); + void Event_EndTeleport(); + void Event_TeleportExit(); + void Event_TeleportEnter(); + void Event_CreatePortal(); + + // Shield + void Event_EnableShield(); + void Event_DisableShield(); + void Event_ForceDisableShield(); + void Event_AssignShieldFx( hhEntityFx* fx ); + void Event_StartHeadFx(); + void Event_EndHeadFx(); + void Event_AssignHeadFx( hhEntityFx* fx ); + + // Telepathic Triggering + void Event_KeeperTrigger(); + + float GetTeleportDestRating(const idVec3 &pos); + idEntityPtr beamTelepathic; // Beam used when we are telepathically controlling an object + idEntityPtr beamAttack; + idEntityPtr shieldFx; + idEntityPtr headFx; + idEntityPtr triggerEntity; + idEntityPtr throwEntity; + int nextShieldImpact; + float shieldAlpha; + bool bThrowing; +#endif +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_mutate.cpp b/neo/prey/Prey/ai_mutate.cpp new file mode 100644 index 00000000..ea7e13ec --- /dev/null +++ b/neo/prey/Prey/ai_mutate.cpp @@ -0,0 +1,63 @@ + + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +CLASS_DECLARATION(hhMonsterAI, hhMutate) + EVENT( MA_OnProjectileLaunch, hhMutate::Event_OnProjectileLaunch ) +END_CLASS + +void hhMutate::Event_OnProjectileLaunch(hhProjectile *proj) { + const function_t *newstate = NULL; + if ( !enemy.IsValid() || !AI_COMBAT ) { + return; + } + + float min = spawnArgs.GetFloat( "dda_dodge_min", "0.3" ); + float max = spawnArgs.GetFloat( "dda_dodge_max", "0.8" ); + float dodgeChance = 0.6f; + + dodgeChance = (min + (max-min)*gameLocal.GetDDAValue() ); + + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "%s dodge chance %f\n", GetName(), dodgeChance ); + } + if ( gameLocal.random.RandomFloat() > dodgeChance ) { + return; + } + + //determine which side to dodge to + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + idVec3 povLeft, povUp; + povToTarget.OrthogonalBasis(povLeft, povUp); + povLeft.Normalize(); + + idVec3 projVel = proj->GetPhysics()->GetLinearVelocity(); + projVel.Normalize(); + float dot = povLeft * projVel; + if ( dot < 0 ) { + newstate = GetScriptFunction( "state_DodgeBackRight" ); + } else { + newstate = GetScriptFunction( "state_DodgeBackLeft" ); + } + + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhMutate::LinkScriptVariables(void) { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_COMBAT ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_mutate.h b/neo/prey/Prey/ai_mutate.h new file mode 100644 index 00000000..c726240b --- /dev/null +++ b/neo/prey/Prey/ai_mutate.h @@ -0,0 +1,15 @@ + +#ifndef __PREY_AI_MUTATE_H__ +#define __PREY_AI_MUTATE_H__ + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +class hhMutate : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhMutate); +protected: + void Event_OnProjectileLaunch(hhProjectile *proj); + void LinkScriptVariables(); + idScriptBool AI_COMBAT; +}; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_mutilatedhuman.cpp b/neo/prey/Prey/ai_mutilatedhuman.cpp new file mode 100644 index 00000000..6928f5b5 --- /dev/null +++ b/neo/prey/Prey/ai_mutilatedhuman.cpp @@ -0,0 +1,390 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MH_AlertFriends("alertFriends", NULL, NULL); +const idEventDef MH_DropBinds("dropBinds"); +const idEventDef MH_DropProjectiles("" ); + +CLASS_DECLARATION(hhMonsterAI, hhMutilatedHuman) + EVENT( MH_AlertFriends, hhMutilatedHuman::Event_AlertFriends ) + EVENT( MH_DropBinds, hhMutilatedHuman::Event_DropBinds ) + EVENT( MH_DropProjectiles, hhMutilatedHuman::Event_DropProjectiles ) +END_CLASS + +void hhMutilatedHuman::Spawn() { + damageFlag = 0; +} + +void hhMutilatedHuman::Event_DropBinds( ) { + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && !ent->IsType( hhProjectile::Type ) && !ent->IsType( idEntityFx::Type ) && ent->GetBindMaster() == this && ent != head.GetEntity() ) { + ent->Unbind(); + next = teamChain; + } + } +} + +void hhMutilatedHuman::Event_DropProjectiles() { + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); + ent->Hide(); + ent->PostEventSec( &EV_Remove, 5 ); + next = teamChain; + } + } +} + +void hhMutilatedHuman::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if (!fl.takedamage) { + return; + } + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if ( AI_LIMB_FELL ) { + PostEventSec( &MH_DropProjectiles, 0.1f ); + } + + if ( AI_DEAD ) { + return; + } + + idDict args; + idVec3 bonePos; + idMat3 boneAxis; + idStr debrisName; + idStr damageGroup = GetDamageGroup( location ); + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool( "ice" ) ) { + return; + } + + if ( !( damageGroup == "head" && damageDef->GetInt( "damage" ) >= spawnArgs.GetFloat( "head_falloff_damage", "70" ) ) ) { + if ( gameLocal.random.RandomFloat() >= spawnArgs.GetFloat( "chanceLimbsWillFallOff", "0.1" ) ) { // CJR: Build in a chance that the limbs will fall off + return; + } + } + + if ( damageGroup == "left_arm" && !(damageFlag & BIT(0)) ) { + damageFlag |= BIT(0); + debrisName = spawnArgs.GetString( "def_debris_arm" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_arm_left" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_left_arm_blood"), spawnArgs.GetString("bone_arm_left") ); + } + } + + } else if ( damageGroup == "right_arm" && !(damageFlag & BIT(1)) ) { + damageFlag |= BIT(1); + debrisName = spawnArgs.GetString( "def_debris_arm" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_arm_right" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_arm_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_right_arm_blood"), spawnArgs.GetString("bone_arm_right") ); + } + } + } else if ( damageGroup == "chest" && !(damageFlag & BIT(2)) ) { + damageFlag |= BIT(2); + damageFlag |= BIT(3); + debrisName = spawnArgs.GetString( "def_debris_chest" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_chest" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_chest_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_chest_blood"), spawnArgs.GetString("bone_chest") ); + } + } + } else if ( damageGroup == "head" ) { + if ( head.IsValid() && !head->IsHidden() ) { + debrisName = spawnArgs.GetString( "def_debris_head" ); + if ( !debrisName.IsEmpty() ) { + idEntity *debris = gameLocal.SpawnObject( debrisName, &args ); + if ( debris ) { + AI_LIMB_FELL = true; + GetJointWorldTransform( spawnArgs.GetString( "bone_head" ), bonePos, boneAxis ); + debris->SetOrigin( bonePos ); + debris->GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "debris_head_velocity" ) * GetAxis() ); + debris->GetPhysics()->SetAngularVelocity( 60 * hhUtils::RandomVector() ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_head_blood"), spawnArgs.GetString("bone_head") ); + } + } + head->Hide(); + } + } + + idStr skinName = spawnArgs.GetString("damage_skin"); + skinName += damageFlag; + if ( damageFlag > 0 ) { + SetSkin( declManager->FindSkin( skinName ) ); + UpdateVisuals(); + } +} + +void hhMutilatedHuman::Event_AlertFriends() { + hhMonsterAI *check; + if ( !enemy.IsValid() ) { + return; + } + + for( int i = 0; i < targets.Num(); i++ ) { + check = static_cast(targets[ i ].GetEntity()); + if ( !check || check->IsHidden() || !check->IsType( hhMutilatedHuman::Type ) ) { + continue; + } + check->SetEnemy( enemy.GetEntity() ); + } +} + +void hhMutilatedHuman::AnimMove( void ) { + //overridden to set enemy if blockedEnt can be an enemy + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); +#ifdef HUMANHEAD //jsh wallwalk + idMat3 oldaxis = GetGravViewAxis(); +#else + idMat3 oldaxis = viewAxis; +#endif + + AI_BLOCKED = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) { + TurnToward( lastVisibleEnemyPos ); + goalPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + goalPos = oldorigin; + } else if ( GetMovePos( goalPos ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( goalPos, newDest ); + TurnToward( newDest ); + } else { + TurnToward( goalPos ); + } + } + + Turn(); + + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( allowMove ) { +#ifdef HUMANHEAD //jsh wallwalk + GetMoveDelta( oldaxis, GetGravViewAxis(), delta ); +#else + GetMoveDelta( oldaxis, viewAxis, delta ); +#endif + } else { + delta.Zero(); + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( disableGravity ); + + RunPhysics(); + + if ( ai_debugMove.GetBool() ) { + // HUMANHEAD JRM - so we can see if grav is on or off + if(disableGravity) { + gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 ); + } else { + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + } + + moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idActor::Type ) && ReactionTo( blockEnt ) != ATTACK_IGNORE ) { + SetEnemy( static_cast(blockEnt) ); + } + if ( blockEnt && blockEnt->IsType( hhPod::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + blockEnt->Damage( this, this, vec3_zero, spawnArgs.GetString( "kick_damage" ), 1.0f, INVALID_JOINT ); + } + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } + } + + BlockedFailSafe(); + + AI_ONGROUND = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +int hhMutilatedHuman::ReactionTo( const idEntity *ent ) { + const idActor *actor = static_cast( ent ); + if( actor && actor->IsType(hhDeathProxy::Type) ) { + return ATTACK_IGNORE; + } + + if ( ent->health <= 0 ) { + return ATTACK_IGNORE; + } + + if ( ent->fl.hidden ) { + // ignore hidden entities + return ATTACK_IGNORE; + } + + if ( !ent->IsType( idActor::Type ) ) { + return ATTACK_IGNORE; + } + + actor = static_cast( ent ); + if ( actor->IsType( idPlayer::Type ) && static_cast(actor)->noclip ) { + // ignore players in noclip mode + return ATTACK_IGNORE; + } + + //only attack spiritwalking players if they hurt me + if ( ent->IsType( hhPlayer::Type ) ) { + const hhPlayer *player = static_cast( ent ); + if ( nextSpiritProxyCheck == 0 && player && player->IsSpiritWalking() ) { + nextSpiritProxyCheck = gameLocal.time + SEC2MS(2); + return ATTACK_ON_DAMAGE; + } + } + + if ( ent->IsType( hhSpiritProxy::Type ) ) { + if ( gameLocal.time > nextSpiritProxyCheck ) { + nextSpiritProxyCheck = 0; + //attack spiritproxy on sight if we have no enemy or if its closer than our current enemy + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + float distToEnemy = (enemy->GetOrigin() - GetOrigin()).LengthSqr(); + float distToProxy = (ent->GetOrigin() - GetOrigin()).LengthSqr(); + if ( distToProxy < distToEnemy ) { + return ATTACK_ON_SIGHT; + } + } else { + return ATTACK_ON_SIGHT; + } + } else { + return ATTACK_IGNORE; + } + } + + if ( ent && ent->spawnArgs.GetBool( "never_target" ) ) { + return ATTACK_IGNORE; + } + + //if we're hurt, its probably because of the player, so keep him as an enemy + if ( actor->IsType( idPlayer::Type ) && health < spawnHealth ) { + return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE | ATTACK_ON_SIGHT; + } + + // 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 + if ( actor->IsType( idPlayer::Type ) ) { + return ATTACK_ON_DAMAGE; + } else { + return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } + } + //force players to only alert through damage + if ( actor->IsType( idPlayer::Type ) ) { + return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } else if ( actor->IsType( idAI::Type ) ) { + //only allow ai actors to otherwise alert them + return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE; + } + } + + // monsters will fight when attacked by lower ranked monsters. rank 0 never fights back. + if ( rank && ( actor->rank < rank ) ) { + return ATTACK_ON_DAMAGE; + } + + // don't fight back + return ATTACK_IGNORE; +} + +/* +===================== +hhMutilatedHuman::Save +===================== +*/ +void hhMutilatedHuman::Save( idSaveGame *savefile ) const { + savefile->WriteInt( damageFlag ); +} + +/* +===================== +hhMutilatedHuman::Restore +===================== +*/ +void hhMutilatedHuman::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( damageFlag ); +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhMutilatedHuman::LinkScriptVariables(void) { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_LIMB_FELL ); +} \ No newline at end of file diff --git a/neo/prey/Prey/ai_mutilatedhuman.h b/neo/prey/Prey/ai_mutilatedhuman.h new file mode 100644 index 00000000..c7c35528 --- /dev/null +++ b/neo/prey/Prey/ai_mutilatedhuman.h @@ -0,0 +1,23 @@ +#ifndef __PREY_AI_MUTILATEDHUMAN_H__ +#define __PREY_AI_MUTILATEDHUMAN_H__ + +class hhMutilatedHuman : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhMutilatedHuman); + void Event_AlertFriends(); + void Event_DropBinds(); + void Event_DropProjectiles(); + int ReactionTo( const idEntity *ent ); + void Spawn(); + void AnimMove(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void LinkScriptVariables(); +protected: + idScriptBool AI_LIMB_FELL; + int damageFlag; +}; + + +#endif //__PREY_AI_MUTILATEDHUMAN_H__ \ No newline at end of file diff --git a/neo/prey/Prey/ai_passageway.cpp b/neo/prey/Prey/ai_passageway.cpp new file mode 100644 index 00000000..789cf44b --- /dev/null +++ b/neo/prey/Prey/ai_passageway.cpp @@ -0,0 +1,144 @@ +// +// ai_passageway.cpp +// +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_EnablePassageway("enable", NULL); +const idEventDef EV_DisablePassageway("disable", NULL); + +// hhAIPassageway +CLASS_DECLARATION(hhAnimated, hhAIPassageway) + EVENT(EV_AnimDone, hhAIPassageway::Event_AnimDone) + EVENT(EV_Activate, hhAIPassageway::Event_Trigger) + EVENT(EV_PostSpawn, hhAIPassageway::PostSpawn) + EVENT(EV_EnablePassageway, hhAIPassageway::Event_EnablePassageway) + EVENT(EV_DisablePassageway, hhAIPassageway::Event_DisablePassageway) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +// +// Spawn() +// +void hhAIPassageway::Spawn() { + timeLastEntered = 0; + lastEntered = NULL; + enabled = TRUE; + hasEntered = FALSE; + + GetPhysics()->SetContents(CONTENTS_SOLID); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +// +// PostSpawn() +// +void hhAIPassageway::PostSpawn() { + SetEnablePassageway(spawnArgs.GetBool("enabled", "1")); +} + +// +// SetEnablePassageway() +// +void hhAIPassageway::SetEnablePassageway(bool tf) { + + // Ignore if we're already in that state + if(enabled == tf) + return; + + fl.refreshReactions = tf; + enabled = tf; +} + +// +// Event_AnimDone() +// +void hhAIPassageway::Event_AnimDone( int animIndex ) { + //When we are done we want to pass ourselves as the activator + activator = this; + + hhAnimated::Event_AnimDone( animIndex ); +} + +// +// Event_Trigger() +// +void hhAIPassageway::Event_Trigger( idEntity *activator ) { + + if(!activator) + return; + + // Ignore being activated by another passage node - they link to eachother, + // so they must be ignored otherwise it will continuously trigger + if( activator->IsType(hhAIPassageway::Type) ) { + return; + } + + // Play our anim + hhAnimated::Event_Activate( activator ); + HH_ASSERT(anim != NULL); + + if( !activator->IsType(hhMonsterAI::Type) ) { + return; + } + + hhMonsterAI *ai = static_cast(activator); + + // Can't enter if dead + if(ai->health <= 0) + return; + + if(!ai->IsInsidePassageway()) + { + //HUMANHEAD PCF mdl 04/29/06 - Added lastEntered check + if(hasEntered && lastEntered.GetEntity() == ai) { + ai->ProcessEvent(&MA_EnterPassageway, this); + hasEntered = FALSE; + } else { + lastEntered = ai; + timeLastEntered = gameLocal.GetTime(); + hasEntered = TRUE; + } + + } + else + { + ai->PostEventMS(&MA_ExitPassageway, 32, this); + hasEntered = FALSE; + } +} + +// +// GetExitPos() +// +idVec3 hhAIPassageway::GetExitPos(void) { + idVec3 pos = GetOrigin(); + idVec3 exitOffset; + spawnArgs.GetVector("exit_offset", "0 0 0", exitOffset); + exitOffset *= GetAxis(); + pos += exitOffset; + pos.z += 0.2f; + return pos; +} + +void hhAIPassageway::Save(idSaveGame *savefile) const { + savefile->WriteInt( timeLastEntered - gameLocal.time ); + lastEntered.Save( savefile ); + savefile->WriteBool( hasEntered ); + savefile->WriteBool( enabled ); +} + +void hhAIPassageway::Restore(idRestoreGame *savefile) { + savefile->ReadInt( timeLastEntered ); + timeLastEntered += gameLocal.time; + + lastEntered.Restore( savefile ); + savefile->ReadBool( hasEntered ); + savefile->ReadBool( enabled ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_passageway.h b/neo/prey/Prey/ai_passageway.h new file mode 100644 index 00000000..88b3b57b --- /dev/null +++ b/neo/prey/Prey/ai_passageway.h @@ -0,0 +1,49 @@ +#ifndef __PREY_AI_PASSAGENODE_H__ +#define __PREY_AI_PASSAGENODE_H__ + + + +// +// hhAIPassageway +// +class hhAIPassageway : public hhAnimated +{ +public: + CLASS_PROTOTYPE(hhAIPassageway); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + virtual void PostSpawn() {}; + void SetEnablePassageway(bool tf) {}; + void Event_AnimDone( int animIndex ) {}; + void Event_Trigger( idEntity *activator ) {}; + void Event_EnablePassageway(void) {} + void Event_DisablePassageway(void) {} +#else + + hhAIPassageway() {timeLastEntered = 0; lastEntered = NULL;} + virtual void Spawn(); + virtual void PostSpawn(); + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + + idVec3 GetExitPos(void); // The point that a monster should be spawned upon exiting this passageway + + void SetEnablePassageway(bool tf); + bool IsPassagewayEnabled(void) const {return enabled;} + + int timeLastEntered; // The time someone last entered this passage node + idEntityPtr lastEntered; // The ai that last entered this passage node + bool hasEntered; // FALSE if the AI has not fully entered yet - waiting for one more trigger + +protected: + void Event_AnimDone( int animIndex ); + void Event_Trigger( idEntity *activator ); + void Event_EnablePassageway(void) {SetEnablePassageway(TRUE);} + void Event_DisablePassageway(void) {SetEnablePassageway(FALSE);} + + bool enabled; // TRUE if we are a valid passagway +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif diff --git a/neo/prey/Prey/ai_possessedTommy.cpp b/neo/prey/Prey/ai_possessedTommy.cpp new file mode 100644 index 00000000..5d9af996 --- /dev/null +++ b/neo/prey/Prey/ai_possessedTommy.cpp @@ -0,0 +1,120 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhMonsterAI, hhPossessedTommy ) +END_CLASS + +// TODO: Set up a rhythmic damaging of this entity. + +void hhPossessedTommy::Spawn() { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + nextDrop = 0; +} + +void hhPossessedTommy::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( attacker->IsType( hhPlayer::Type ) ) { // Being attacked by a player, must be a spiritwalking player + if ( possessedProxy.IsValid() ) { + hhSpiritProxy *proxy = possessedProxy.GetEntity(); + + Hide(); + + proxy->SetOrigin( GetOrigin() ); + proxy->SetAxis( GetAxis() ); + proxy->Show(); + proxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + + if ( proxy->GetPlayer() ) { + proxy->GetPlayer()->Unpossess(); + } + } + + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + PostEventMS( &EV_Remove, 0 ); + + return; + } + + // OTHERWISE, KILL THIS ENTITY AND KILL THE PLAYER -- create a death proxy here, etc + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + // Kill the player + if (possessedProxy.IsValid() ) { + if ( possessedProxy->GetPlayer() ) { + possessedProxy->GetPlayer()->PossessKilled(); + } + } +} + +void hhPossessedTommy::Event_Remove(void) { + if (!fl.hidden && possessedProxy.IsValid()) { + Hide(); + + hhSpiritProxy *proxy = possessedProxy.GetEntity(); + + proxy->SetOrigin( GetOrigin() ); + proxy->SetAxis( GetAxis() ); + proxy->Show(); + proxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + + if ( proxy->GetPlayer() ) { + proxy->GetPlayer()->Unpossess(); + } + } + hhMonsterAI::Event_Remove(); +} + +void hhPossessedTommy::Think(void) { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { + return; + } + + hhMonsterAI::Think(); + if (possessedProxy.IsValid() && gameLocal.time > nextDrop) { + hhPlayer *player = possessedProxy->GetPlayer(); + if (player) { + player->SetHealth(player->GetHealth() - 1); + if (player->GetHealth() <= 0) { + Hide(); + + possessedProxy->SetOrigin( GetOrigin() ); + possessedProxy->SetAxis( GetAxis() ); + possessedProxy->Show(); + possessedProxy->GetPhysics()->SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + + player->Unpossess(); + + player->Killed(this, this, 1, vec3_origin, INVALID_JOINT); + PostEventMS(&EV_Remove, 0); + } + } + + float min = spawnArgs.GetFloat( "dda_drain_min" ); + float max = spawnArgs.GetFloat( "dda_drain_max" ); + nextDrop = gameLocal.time + min + (max - min) * (1.0f - gameLocal.GetDDAValue()); + } +} + +void hhPossessedTommy::Save( idSaveGame *savefile ) const { + possessedProxy.Save( savefile ); +} + +void hhPossessedTommy::Restore( idRestoreGame *savefile ) { + possessedProxy.Restore( savefile ); + nextDrop = 0; +} + +void hhPossessedTommy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + // Turn to a different direction + float adjust = 45.0f + (gameLocal.random.RandomFloat() * 30.0f); + if (gameLocal.random.RandomInt(100) > 50) { + adjust = -adjust; + } + gameLocal.Printf("Adjusting by %f\n", adjust); + TurnToward(current_yaw + adjust); + move.nextWanderTime = 0; +} + diff --git a/neo/prey/Prey/ai_possessedTommy.h b/neo/prey/Prey/ai_possessedTommy.h new file mode 100644 index 00000000..8c061351 --- /dev/null +++ b/neo/prey/Prey/ai_possessedTommy.h @@ -0,0 +1,30 @@ +#ifndef __PREY_AI_POSSESSED_TOMMY_H__ +#define __PREY_AI_POSSESSED_TOMMY_H__ + +/*********************************************************************** + hhPossessedTommy. +***********************************************************************/ +class hhPossessedTommy : public hhMonsterAI { + +public: + CLASS_PROTOTYPE( hhPossessedTommy ); + +public: + void Spawn(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Think(void); + + void SetPossessedProxy( hhSpiritProxy *newProxy ) { possessedProxy = newProxy; } + + virtual void Event_Remove(void); + + 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 ); +protected: + idEntityPtr possessedProxy; + int nextDrop; +}; + +#endif //__PREY_AI_POSSESSED_TOMMY_H__ \ No newline at end of file diff --git a/neo/prey/Prey/ai_reaction.cpp b/neo/prey/Prey/ai_reaction.cpp new file mode 100644 index 00000000..ca87410d --- /dev/null +++ b/neo/prey/Prey/ai_reaction.cpp @@ -0,0 +1,639 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/////////////////////////////////////////// +// +// hhReactionDesc +// +/////////////////////////////////////////// + + +// +// CausedToStr() +// +idStr hhReactionDesc::CauseToStr(Cause c) { + switch(c) { + case Cause_Damage: + return idStr("Damage"); + case Cause_Touch: + return idStr("Touch"); + case Cause_Use: + return idStr("Use"); + case Cause_None: + return idStr("None"); + case Cause_PlayAnim: + return idStr("PlayAnim"); + case Cause_Telepathic_Trigger: + return idStr("TelepathicTrigger"); + case Cause_Telepathic_Throw: + return idStr("TelepathicThrow"); + } + + return idStr("!!UNDEFINED!!"); +} + +// +// EffectToStr() +// +idStr hhReactionDesc::EffectToStr(Effect e) { + switch(e) { + case Effect_Damage: + return idStr("Damage"); + case Effect_DamageEnemy: + return idStr("DamageEnemy"); + case Effect_VehicleDock: + return idStr("VehicleDock"); + case Effect_Vehicle: + return idStr("Vehicle"); + case Effect_Heal: + return idStr("Heal"); + case Effect_HaveFun: + return idStr("HaveFun"); + case Effect_ProvideCover: + return idStr("ProvideCover"); + case Effect_BroadcastTargetMsgs: + return idStr("BroadcastTargetMsgs"); + case Effect_Climb: + return idStr("Climb"); + case Effect_Passageway: + return idStr("Passageway"); + case Effect_CallBackup: + return idStr("CallBackup"); + } + + return idStr("!!UNDEFINED!!"); +} + +// +// StrToCause() +// +hhReactionDesc::Cause hhReactionDesc::StrToCause(const char* str) { + for(int i=0;i 1) { + flags |= flagReq_KeyValue; + int firstSpace = keyString.Find(' '); + if( firstSpace >= 0 ) { + key = keyString.Left( firstSpace); + keyVal = keyString.Right( keyString.Length() - firstSpace -1); + } + } + else + gameLocal.Error("Invalide key for 'reaction_req_ley' value: %s", (const char*)keyString); + } +//MDC begin + keyString.Empty(); + if( keys.GetString( "finish_key", "", keyString)) { + if( keyString.Length() > 1 ) { + int firstSpace = keyString.Find(' '); + if( firstSpace >= 0 ) { + finish_key = keyString.Left( firstSpace ); + finish_val = keyString.Right( keyString.Length() - firstSpace - 1 ); + } + } + } +//MDC end + + idStr animString(""); + if(keys.GetString("req_anim", "", animString)) { + flags |= flagReq_Anim; + anim = animString; + } + + if(keys.GetBool("req_rangeattack")) { + flags |= flagReq_RangeAttack; + } + if(keys.GetBool("req_meleeattack")) { + flags |= flagReq_MeleeAttack; + } + if(keys.GetBool("req_can_see")) { + flags |= flagReq_CanSee; + } + + if(keys.GetBool("req_telepathic") || cause == hhReactionDesc::Cause_Telepathic_Throw || cause == hhReactionDesc::Cause_Telepathic_Trigger) { + flags |= flagReq_Telepathic; + } + + + // Effect volumes + effectVolumes.Clear(); + const idKeyValue *kv = keys.MatchPrefix("effect_volume", NULL); + while(kv) { + idStr effectVolName = kv->GetValue(); + if(effectVolName.Length() > 0) { + idEntity *e = gameLocal.FindEntity(effectVolName.c_str()); + if(!e) { + gameLocal.Error("Failed to find effect_volume named %s", effectVolName.c_str()); + } + if(!e->IsType(hhReactionVolume::Type)) { + gameLocal.Error("effect_volume named %s was of incorrect spawn type. Must be hhReactionVolume", effectVolName.c_str()); + } + + effectVolumes.AddUnique(static_cast(e)); + } + + // Move to next volume + kv = keys.MatchPrefix("effect_volume", kv); + } + + listenerRadius = keys.GetFloat("listener_radius", "-1.0f"); + listenerMinRadius = keys.GetFloat("listener_min_radius", "-1.0f"); + + // Store listener volumes + listenerVolumes.Clear(); + kv = keys.MatchPrefix("listener_volume", NULL); + while(kv) { + idStr listenVolName = kv->GetValue(); + if(listenVolName.Length() > 0) { + idEntity *e = gameLocal.FindEntity(listenVolName.c_str()); + if(!e) { + gameLocal.Error("Failed to find listener_volume named %s", listenVolName.c_str()); + } + if(!e->IsType(hhReactionVolume::Type)) { + gameLocal.Error("listener_volume named %s was of incorrect spawn type. Must be hhReactionVolume", listenVolName.c_str()); + } + + listenerVolumes.AddUnique(static_cast(e)); + } + + // Move to next volume + kv = keys.MatchPrefix("listener_volume", kv); + } + + // Store touchdir + touchDir = keys.GetString("touchdir",""); + + // Extract out touch offsets + kv = keys.MatchPrefix("touchoffset_", NULL); + touchOffsets.SetGranularity(1); + touchOffsets.Clear(); + idStr tmpStr; + idStr keyName; + int usIndex; + while(kv) { + tmpStr = kv->GetKey(); + usIndex = tmpStr.FindChar("touchoffset_", '_'); + keyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + touchOffsets.Set(keyName, kv->GetValue()); + + // Move to the next + kv = keys.MatchPrefix("touchoffset_", kv); + } + + // Extract out touch bounds + kv = keys.MatchPrefix("safebound_", NULL ); + while(kv) { + tmpStr = kv->GetKey(); + usIndex = tmpStr.FindChar("safebound_", '_'); + keyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + touchOffsets.Set(keyName, kv->GetValue()); + kv = keys.MatchPrefix("safebound_", kv); + } +} + +/* +================ +hhReactionDesc::Save +================ +*/ +void hhReactionDesc::Save(idSaveGame *savefile) const { + savefile->WriteString(name); + savefile->WriteInt(effect); + savefile->WriteInt(cause); + savefile->WriteInt(flags); + savefile->WriteFloat(effectRadius); + savefile->WriteFloat(effectMinRadius); + + int num = effectVolumes.Num(); + savefile->WriteInt(num); + for(int i = 0; i < num; i++) { + savefile->WriteObject( effectVolumes[i] ); + } + + savefile->WriteString(key); + savefile->WriteString(keyVal); + savefile->WriteString(anim); + savefile->WriteFloat(listenerRadius); + savefile->WriteFloat(listenerMinRadius); + + num = listenerVolumes.Num(); + savefile->WriteInt(num); + for(int i = 0; i < num; i++) { + savefile->WriteObject(listenerVolumes[i]); + } + + savefile->WriteDict(&touchOffsets); + savefile->WriteString(touchDir); + savefile->WriteString(finish_key); + savefile->WriteString(finish_val); +} + +/* +================ +hhReactionDesc::Restore +================ +*/ +void hhReactionDesc::Restore( idRestoreGame *savefile ) { + savefile->ReadString(name); + savefile->ReadInt(reinterpret_cast (effect)); + savefile->ReadInt(reinterpret_cast (cause)); + savefile->ReadInt(reinterpret_cast (flags)); + savefile->ReadFloat(effectRadius); + savefile->ReadFloat(effectMinRadius); + + int num; + savefile->ReadInt(num); + effectVolumes.SetNum(num); + for(int i = 0; i < num; i++) { + savefile->ReadObject(reinterpret_cast (effectVolumes[i])); + } + + savefile->ReadString(key); + savefile->ReadString(keyVal); + savefile->ReadString(anim); + savefile->ReadFloat(listenerRadius); + savefile->ReadFloat(listenerMinRadius); + + savefile->ReadInt(num); + listenerVolumes.SetNum(num); + for(int i = 0; i < num; i++) { + savefile->ReadObject(reinterpret_cast (listenerVolumes[i])); + } + + savefile->ReadDict(&touchOffsets); + savefile->ReadString(touchDir); + savefile->ReadString(finish_key); + savefile->ReadString(finish_val); +} + +/////////////////////////////////////////// +// +// hhReactionVolume +// +/////////////////////////////////////////// +CLASS_DECLARATION(idEntity, hhReactionVolume) +END_CLASS + +// +// Spawn() +// +void hhReactionVolume::Spawn() { + + flags = 0; + + if(spawnArgs.GetBool("players", "1")) { + flags |= flagPlayers; + } + + if(spawnArgs.GetBool("monsters", "1")) { + flags |= flagMonsters; + } + + if(flags == 0) { + gameLocal.Error("Reaction volume %s is not targetting players or monsters - Why even have this volume?", (const char*)name); + } + +} + +bool hhReactionVolume::IsValidEntity( idEntity *ent ) { + assert(GetPhysics() != NULL); + assert(flags != 0); // have to be looking for something - else whats the point? + + if ( !ent || ent->IsHidden() ) { + return false; + } + if(flags & flagMonsters && !ent->IsType(idAI::Type)) { + if(flags & flagPlayers && !ent->IsType(hhPlayer::Type)) { + return false; + } + } + idEntity * entityList[ MAX_GENTITIES ]; + int numEnts = gameLocal.clip.EntitiesTouchingBounds(GetPhysics()->GetAbsBounds(), CONTENTS_BODY, entityList, MAX_GENTITIES); + for(int i=0;i &outValidEnts) { + + assert(GetPhysics() != NULL); + assert(flags != 0); // have to be looking for something - else whats the point? + + idEntity * entityList[ MAX_GENTITIES ]; + + int numEnts = gameLocal.clip.EntitiesTouchingBounds(GetPhysics()->GetAbsBounds(), CONTENTS_BODY, entityList, MAX_GENTITIES); + for(int i=0;iIsHidden()) + continue; + if(flags & flagPlayers && entityList[i]->IsType(hhPlayer::Type)) { + outValidEnts.Append(entityList[i]); + if(ai_debugBrain.GetInteger()) { + gameRenderWorld->DebugArrow(colorMagenta, GetOrigin(), entityList[i]->GetOrigin(), 10, 1000); + } + continue; + } + } + + if(ai_debugBrain.GetInteger()) { + gameRenderWorld->DebugBounds(colorWhite, GetPhysics()->GetBounds(), GetOrigin(), 1000); + } +} + +/* +================ +hhReactionVolume::Save +================ +*/ +void hhReactionVolume::Save(idSaveGame *savefile) const { + savefile->WriteInt(flags); +} + +/* +================ +hhReactionVolume::Restore +================ +*/ +void hhReactionVolume::Restore(idRestoreGame *savefile) { + savefile->ReadInt(flags); +} + +/////////////////////////////////////////// +// +// hhReactionHandler +// +/////////////////////////////////////////// + + +// +// construction +// +hhReactionHandler::hhReactionHandler() { +} + + +// +// destruction +// +hhReactionHandler::~hhReactionHandler() { + reactionDescs.DeleteContents(TRUE); +} + +// +// LoadReactionDesc() +// +const hhReactionDesc* hhReactionHandler::LoadReactionDesc(const char* defName, const idDict *uniqueDescKeys) { + + // Ignore null reactions + if ( !defName || !defName[0] ) { + return NULL; + } + + const hhReactionDesc *ret = NULL; + + // If we do not have unqiue keys, we can just use a cached version + if(!uniqueDescKeys) { + ret = FindReactionDesc(defName); + if(ret) { + return ret; + } + } + + // Get the def dict + const idDict *reactDefDict = gameLocal.FindEntityDefDict(defName); + if(!reactDefDict) { + gameLocal.Error("Failed to find reaction dict named %s", defName); + } + + // Build the keys that will be passed onto the creation + idDict finalDict; + finalDict.Copy(*reactDefDict); + StripReactionPrefix(finalDict); + if(uniqueDescKeys) { + idDict uniqueCopy(*uniqueDescKeys); + StripReactionPrefix(uniqueCopy); + finalDict.Copy(uniqueCopy); + } + + hhReactionDesc* react = CreateReactionDesc(defName, finalDict); + return react; +} + +// +// createReactionDesc() +// +hhReactionDesc* hhReactionHandler::CreateReactionDesc(const char *desiredName, const idDict &keys) { + + idStr finalName = GetUniqueReactionDescName(desiredName); + hhReactionDesc *desc = new hhReactionDesc(); + + desc->name = finalName; + desc->Set(keys); + + reactionDescs.Append(desc); + return desc; +} + +// +// getUniqueReactionDescName() +// +idStr hhReactionHandler::GetUniqueReactionDescName(const char *name) { + + int count = 0; + idStr finalName(name); + while(1) { + if(count > 0) { + finalName = idStr(name) + idStr("_") + idStr(count); + } + + // Did we find a name that was not taken? + if(FindReactionDesc(finalName.c_str()) == NULL) { + return finalName; + } +#ifdef _DEBUG + if(count > 128) { + assert(FALSE); // How the hell did we get so many copies? + } +#endif + + count++; + } +} + +// +// findReactionDesc() +// +const hhReactionDesc* hhReactionHandler::FindReactionDesc(const char *name) { + + for(int i=0;iname.c_str(), name) == 0) { + return reactionDescs[i]; + } + } + + return NULL; +} + +// +// stripReactionPrefix() +// +void hhReactionHandler::StripReactionPrefix(idDict &dict) { + const idKeyValue *kv = NULL;//dict.MatchPrefix("reaction"); + + idDict newDict; + + for(int i=0;iGetKey(); + + // Do we have a reaction token to strip out? + if(idStr::FindText(key.c_str(), "reaction") != -1) { + + int endPrefix = idStr::FindChar(key.c_str(), '_'); + if(endPrefix == -1) { + gameLocal.Error("reactionX_ prefix not found."); + } + idStr realKey(key); + realKey = key.Mid(endPrefix+1, key.Length() - endPrefix - 1); + key = realKey; + } + + //dict.Delete(kv->GetKey().c_str()); + newDict.Set(key.c_str(), kv->GetValue()); + + kv = dict.MatchPrefix("reaction", kv); + } + + + dict.Clear(); + dict.Copy(newDict); +} + +void hhReactionHandler::Save(idSaveGame *savefile) const { + savefile->WriteInt(reactionDescs.Num()); + for (int i = 0; i < reactionDescs.Num(); i++) { + reactionDescs[i]->Save(savefile); + } +} + +void hhReactionHandler::Restore( idRestoreGame *savefile ) { + reactionDescs.DeleteContents(true); + int num; + savefile->ReadInt(num); + reactionDescs.SetNum(num); + for (int i = 0; i < num; i++) { + reactionDescs[i] = new hhReactionDesc(); + reactionDescs[i]->Restore(savefile); + } +} + +void hhReaction::Save(idSaveGame *savefile) const { + savefile->WriteBool(active); + exclusiveOwner.Save(savefile); + savefile->WriteInt(exclusiveUntil); + if (desc) { + savefile->WriteString(desc->name); + } else { + savefile->WriteString(""); + } + causeEntity.Save(savefile); + int num = effectEntity.Num(); + savefile->WriteInt(num); + for(int i = 0; i < num; i++) { + effectEntity[i].ent.Save(savefile); + savefile->WriteFloat(effectEntity[i].weight); + } +} + +void hhReaction::Restore(idRestoreGame *savefile) { + savefile->ReadBool(active); + exclusiveOwner.Restore(savefile); + savefile->ReadInt(exclusiveUntil); + + idStr name; + savefile->ReadString(name); + desc = gameLocal.GetReactionHandler()->FindReactionDesc(name); + causeEntity.Restore(savefile); + int num; + savefile->ReadInt(num); + hhReactionTarget ent; + for(int i = 0; i < num; i++) { + ent.ent.Restore(savefile); + savefile->ReadFloat(ent.weight); + effectEntity.Append(ent); + } +} + diff --git a/neo/prey/Prey/ai_reaction.h b/neo/prey/Prey/ai_reaction.h new file mode 100644 index 00000000..bdbebfc4 --- /dev/null +++ b/neo/prey/Prey/ai_reaction.h @@ -0,0 +1,261 @@ +#ifndef __HH_REACTION_H +#define __HH_REACTION_H + + +// +// hhReactionTarget +// +class hhReactionTarget +{ +public: + hhReactionTarget() { + weight = 1.0f; + ent = NULL; + } + + hhReactionTarget(idEntity *e, float w = 1.0f) { + ent = e; + weight = w; + } + + idEntityPtr ent; // The entity that is effected + float weight; // The degree ent is effected +}; + +// +// hhReactionVolume +// +class hhReactionVolume : public idEntity { +public: + CLASS_PROTOTYPE( hhReactionVolume ); + + enum { + flagPlayers = (1<<0), + flagMonsters = (1<<1), + flagActors = (flagPlayers|flagMonsters), + }; + + void Spawn(void); + + // Gets all of the entities (filtered by validFlags) that are inside this volume + void GetValidEntitiesInside(idList &outValidEnts); + bool IsValidEntity( idEntity *ent ); + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + +protected: + int flags; // Flags for this volume +}; + +// +// hhReactionDesc +// +// Holds the unchanging properties of a reaction +// +class hhReactionDesc +{ +public: + friend class hhReactionHandler; + hhReactionDesc() { + name = idStr(""); + effect = Effect_Invalid; + cause = Cause_Invalid; + flags = 0; + effectRadius = -1.0f; + effectMinRadius = -1.0f; + effectVolumes.SetGranularity(1); + key = idStr(""); + keyVal = idStr(""); + anim = idStr(""); + listenerRadius = -1.0f; + listenerMinRadius = -1.0f; + listenerVolumes.SetGranularity(1); + } + + enum Cause { + Cause_Invalid = -1, + Cause_Damage, // AI can damage this to get reaction + Cause_Touch, // AI can touch this to get reaction + Cause_Use, // AI can 'use' this to get reaction + Cause_None, // AI does nothing, and the effect is always felt + Cause_PlayAnim, // AI must play an anim to get the desired effect + Cause_Telepathic_Trigger, // AI must use their telepathy to trigger this entity + Cause_Telepathic_Throw, // AI must use their telepathy to physically throw this entity + + Cause_Total + }; + + enum Effect { + Effect_Invalid = -1, + Effect_Damage, // Causes damage (reduces health) + Effect_DamageEnemy, // Causes damage (reduces health) + Effect_VehicleDock, // Causes damage (reduces health) + Effect_Vehicle, // Causes damage (reduces health) + Effect_Heal, // Heals entity (increases health) + Effect_HaveFun, // Something 'fun' for this entity to do + Effect_ProvideCover, // Indicates this entity can possibily provide 'cover' for a monster + Effect_BroadcastTargetMsgs, // This entity will have its target's msg's sent + Effect_Climb, // This entity will let the monster climb it. + Effect_Passageway, // This entity is a harvester passageway + Effect_CallBackup, // Calls for help + + Effect_Total + }; + + enum { + flag_EffectAllPlayers = (1<<0), // This entity automatically targets ALL entities of type hhPlayer + flag_EffectAllMonsters = (1<<1), // This entity automatically targets ALL entities of typehhAI + flag_Exclusive = (1<<2), // This msg is exclusive, and once a monster has committed to it - NO one else should listen to it + flag_EffectListener = (1<<3), // The effect of this reaction is applied to the listener + flag_AnimFaceCauseDir = (1<<4), // When playing an anim for this reaction, face the direction of our cause + flag_AnimTriggerCause = (1<<5), // When playing an anim for this reaction, trigger the cause entity + flag_SnapToPoint = (1<<6), // This msg is only for monsters that are telepathic + flagReq_MeleeAttack = (1<<8), // This msg is for monsters that can melee attack + flagReq_RangeAttack = (1<<9), // This msg is for monsters that can range attack + flagReq_Anim = (1<<10), // This msg is for monsters that have a given anim (anim) + flagReq_KeyValue = (1<<11), // This msg is for monsters that have a given key set (key, keyVal) + flagReq_NoVehicle = (1<<12), // This msg is for monsters that are NOT in vehicles + flagReq_CanSee = (1<<13), // This msg is only for monsters that can see the cause entity + flagReq_Telepathic = (1<<14), // This msg is only for monsters that are telepathic + }; + + + static idStr CauseToStr(Cause c); // Converts activate enum into readable form + static idStr EffectToStr(Effect e); // Converts reaction enum to readable form + static Cause StrToCause(const char* str); // Converts string to caused by enum or Caused_Invalid if not found + static Effect StrToEffect(const char* str); // Converts string to effect enum or Effect_Invalid if not found + void Set(const idDict &keys); // Sets this description to the given key values + bool CauseRequiresPathfinding(void) const {return cause == Cause_Touch || cause == Cause_Use || cause == Cause_PlayAnim;} + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idStr name; // unique name for each reaction desc + Effect effect; // pdm: for reaction system + Cause cause; // pdm: for reaction system + unsigned int flags; // jrm: reaction flags for reaction system + float effectRadius; // jrm: If > 0.0f then ONLY target entities that are at most this far away + float effectMinRadius; // jrm: if > 0.0f then ONLY target entities that are at least this far away + idList effectVolumes; // jrm: if valid, then only entities that are inside one of these volumes are valid 'effect' entities + idStr key; // jrm: the key required for a monster to be eligable for this reaction msg (ReactionFlag_KeyReq must be set) + idStr keyVal; // jrm: the value of the reaction key required + idStr anim; // jrm: the value of the reaction key required + float listenerRadius; // jrm: listeners farther away than this distance will not receive this reaction + float listenerMinRadius; // jrm: listeners closer than this dist will not receive this reaction + idList listenerVolumes; // jrm: only listeners that are inside one of these volumes will receive this reaction + idDict touchOffsets; // Example: Use "monster_hunter" as key to get touchOffset values + idStr touchDir; // Indicates how a monster should approach the cause entity + + idStr finish_key; // mdc: used to set a key on monster after finishing a reaction + idStr finish_val; // mdc: used to set a key on monster after finishing a reaction +}; + +// +// hhReaction +// +// Describes a reaction ready to be processed by the AI. Holds a description of the reaction, as well as the cause/effect entities. +// Do the cause describe by to to get the effect in applied to +// +class hhReaction +{ +public: + hhReaction() + { + desc = NULL; + causeEntity = NULL; + active = TRUE; + exclusiveOwner = NULL; + exclusiveUntil = -1; + effectEntity.Clear(); + }; + hhReaction(const hhReactionDesc *reactDesc, idEntity *cause, const hhReactionTarget &effect) + { + active = TRUE; + desc = reactDesc; + causeEntity = cause; + exclusiveUntil = -1; + exclusiveOwner = NULL; + effectEntity.Append(effect); + } + + bool IsActive() const {return active;} + bool IsActiveForListener(idEntity *listener) const { + if(!active) { + return FALSE; + } + + // If someone has claimed this, and their claim is still valid... Only active for that particular AI + if(exclusiveOwner.GetEntity() && gameLocal.GetTime() < exclusiveUntil) { + return exclusiveOwner.GetEntity() == listener; + } + + // No one has claimed us, or time has expired, so anyone can claim us now + return TRUE; + }; + + bool ClaimExclusivity(idEntity *claimer, int ms=1000) { + + // Cannot claim, someone else already has it + if(exclusiveOwner.GetEntity() && gameLocal.GetTime() < exclusiveUntil) { + return FALSE; + } + + // They claimed it now.... + exclusiveOwner = claimer; + exclusiveUntil = gameLocal.GetTime() + ms; + return TRUE; + } + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + + bool active; // TRUE to be considered for sending + idEntityPtr exclusiveOwner; // The AI that has 'claimed' this reaction + int exclusiveUntil; // Until gameLocal.time has reached this value, this reaction does not send msgs (except for exclusive reactions) + const hhReactionDesc* desc; // Info about the kind of reaction this is + idEntityPtr causeEntity; // The entity to apply the cause to + idList effectEntity; // The effect of doing the cause is felt by these entities +}; + + +// +// hhReactionHandler +// +// Manages all of the reaction descs currently loaded +// +class hhReactionHandler +{ +public: + hhReactionHandler(); + virtual ~hhReactionHandler(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // Loads the given reaction def name. If uniqueDescKeys are specified, a unique hhReactionDesc* is guarenteed to be returned. + // Otherwise, if no override keys are specified and a reaction of the given name is found, it is returned without creating a new one + // The returned desc is ONLY a desc, it is not meant to be modified in any way + const hhReactionDesc* LoadReactionDesc(const char* defName, const idDict &uniqueDescKeys) { + if(uniqueDescKeys.GetNumKeyVals() <= 0) { + return LoadReactionDesc(defName, NULL); + } else { + return LoadReactionDesc(defName, &uniqueDescKeys); + } + } + const hhReactionDesc* LoadReactionDesc(const char* defName) {return LoadReactionDesc(defName, NULL);} + +protected: + friend class hhReaction; + + // Creates a new reaction desc with the given name and keys to describe the reaction + hhReactionDesc* CreateReactionDesc(const char *name, const idDict &keys); + idStr GetUniqueReactionDescName(const char *name); + const hhReactionDesc* FindReactionDesc(const char *name); + void StripReactionPrefix(idDict &dict); // Removes any "reaction_" or "reaction1_" prefix from the given dict + const hhReactionDesc* LoadReactionDesc(const char* defName, const idDict *uniqueDescKeys); + + + idList reactionDescs; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_spawncase.cpp b/neo/prey/Prey/ai_spawncase.cpp new file mode 100644 index 00000000..814e2ccc --- /dev/null +++ b/neo/prey/Prey/ai_spawncase.cpp @@ -0,0 +1,227 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// +// hhAISpawnCase +// + +const idEventDef EV_CreateAI( "", NULL); +const idEventDef EV_AutoTrigger( "", NULL); + +CLASS_DECLARATION( hhAnimated, hhAISpawnCase ) + EVENT( EV_Activate, hhAISpawnCase::Event_Trigger ) + EVENT( EV_CreateAI, hhAISpawnCase::Event_CreateAI ) + EVENT( EV_AutoTrigger, hhAISpawnCase::Event_AutoTrigger ) +END_CLASS + +// +// Spawn() +// +void hhAISpawnCase::Spawn( void ) { + + triggerToggle = FALSE; + aiSpawnCount = 0; + waitingForAutoTrigger = FALSE; + triggerQueue = 0; + + CreateEntSpawnArgs(); + + GetPhysics()->SetContents(CONTENTS_SHOOTABLE|CONTENTS_PLAYERCLIP|CONTENTS_MOVEABLECLIP|CONTENTS_IKCLIP|CONTENTS_SHOOTABLEBYARROW); + + // Next frame, create the AI to attach to this case - Wait a frame so that we can copy targets + PostEventMS(&EV_CreateAI, 0); +} + +// +// Event_CreateAI() +// +void hhAISpawnCase::Event_CreateAI( void ) { + + // Spawn the monster + const char *monsterName = spawnArgs.GetString("def_monster", NULL); + int i; + if ( aiSpawnCount < spawnArgs.GetInt("max_monsters", "1") && monsterName && monsterName[0]) { + idDict args = entSpawnArgs; + + idVec3 offset = spawnArgs.GetVector("monster_offset", "0 0 0"); + offset *= GetPhysics()->GetAxis(); + + args.SetVector( "origin", GetPhysics()->GetOrigin() + offset); + args.SetBool("encased", TRUE); + args.Set("encased_idle", spawnArgs.GetString("anim_encased_monster_idle", "encased_idle")); + args.Set("encased_exit", spawnArgs.GetString("anim_encased_monster_exit", "encased_exit")); + args.Set( "encased_exit_offset", spawnArgs.GetString( "encased_exit_offset", "0 0 0" ) ); + args.SetMatrix("rotation", GetAxis()); + encasedAI = static_cast(gameLocal.SpawnObject(monsterName, &args)); + if(!encasedAI.GetEntity()) { + //gameLocal.Warning("No monster specified for case"); + return; + } + + // Copy the targets that our case has to our newly spawned monster + for( i = 0; i < targets.Num(); i++ ) { + encasedAI->targets.AddUnique(targets[i]); + } + if ( spawnArgs.GetBool( "rotated" ) ) { + encasedAI->SetAxis( GetAxis() ); + encasedAI->GetPhysics()->SetAxis( GetAxis() ); + encasedAI->GetRenderEntity()->axis = GetAxis(); + encasedAI->viewAxis = GetAxis(); + encasedAI->Bind(this, true); + } else { + encasedAI->viewAxis = GetAxis(); + encasedAI->Bind(this, FALSE); + } + aiSpawnCount++; + + if(spawnArgs.GetBool("monster_hide", "0")) { + encasedAI->ProcessEvent(&EV_Hide); + } + } +} + +// +// Event_Trigger() +// +void hhAISpawnCase::Event_Trigger( idEntity *activator ) { + bool triggeredAI = false; + bool showedAI = false; + + // We are waiting for an auto-trigger, so don't fire now - but remember that we need to later + if(waitingForAutoTrigger) { + triggerQueue++; + //gameLocal.Printf("\nQUEUED TRIGGERS: %i", triggerQueue); + return; + } + + //gameLocal.Printf("\n * TRIGGERED *"); + if(encasedAI.GetEntity()) { + if(encasedAI->IsHidden()) { + encasedAI->ProcessEvent(&EV_Show); + showedAI = true; + } + + //if triggered_spawn is set, spawn the encasedAI on the first trigger, + //and but don't activate it until a second trigger + if ( !showedAI || !spawnArgs.GetBool( "triggered_spawn", "0" ) ) { + encasedAI->Unbind(); + // removed since monster_offset should take care of this + //encasedAI->SetOrigin(encasedAI->GetOrigin() + encasedAI->viewAxis[0] * 64); + encasedAI.GetEntity()->ProcessEvent(&EV_Activate, activator); + triggeredAI = true; + } + } + + // Start the anim playing + hhAnimated::Event_Activate(activator); + + if ( spawnArgs.GetInt( "no_anim", "0" ) ) { + ProcessEvent(&EV_CreateAI); + //if triggered_spawn is set, dont show the next AI until the next trigger + if ( encasedAI.IsValid() && triggeredAI && spawnArgs.GetBool( "triggered_spawn", "0" ) ) { + encasedAI->ProcessEvent(&EV_Hide); + } + } + triggerToggle = !triggerToggle; +} + +// +// Event_AutoTrigger() +// +void hhAISpawnCase::Event_AutoTrigger( void ) { + waitingForAutoTrigger = FALSE; + ProcessEvent(&EV_Activate, this); +} + +// +// Event_AnimDone() +// +void hhAISpawnCase::Event_AnimDone( int animIndex ) { + + // Call back first + hhAnimated::Event_AnimDone(animIndex); + + const char *n = NULL; + + // Door is OPEN, now we queue up the CLOSE anim + if(triggerToggle) { + n = spawnArgs.GetString("anim_retrigger"); + + // Should we automatically retrigger? (ie. close the door after it was opened?) + if(spawnArgs.GetFloat("auto_retrigger_delay", "-1") >= 0.0f ) { + int delay = SEC2MS(spawnArgs.GetFloat("auto_retrigger_delay", "-1")); + waitingForAutoTrigger = TRUE; + PostEventMS(&EV_AutoTrigger, delay); + } + } + // Door is CLOSED, now we queue up the OPEN anim + else { + + // If we have queued triggers saved up - then lets fire one now since we are now closed + if(triggerQueue > 0) { + triggerQueue--; + PostEventMS(&EV_Activate, 0, this); + //gameLocal.Printf("\nQUEUED TRIGGERS: %i", triggerQueue); + } + n = spawnArgs.GetString("anim"); + int maxMonsters = spawnArgs.GetInt("max_monsters", "1"); + if(maxMonsters < 0 || aiSpawnCount < maxMonsters) { + Event_CreateAI(); + } else { + //gameLocal.Printf("\nMax monsters reached."); + } + } + + anim = GetAnimator()->GetAnim( n ); + HH_ASSERT( anim ); +} + +/* +================ +hhAISpawnCase::Save +================ +*/ +void hhAISpawnCase::Save( idSaveGame *savefile ) const { + encasedAI.Save( savefile ); + savefile->WriteBool( triggerToggle ); + savefile->WriteInt( aiSpawnCount ); + savefile->WriteBool( waitingForAutoTrigger ); + savefile->WriteInt( triggerQueue ); +} + +/* +================ +hhAISpawnCase::Restore +================ +*/ +void hhAISpawnCase::Restore( idRestoreGame *savefile ) { + encasedAI.Restore( savefile ); + savefile->ReadBool( triggerToggle ); + savefile->ReadInt( aiSpawnCount ); + savefile->ReadBool( waitingForAutoTrigger ); + savefile->ReadInt( triggerQueue ); + + CreateEntSpawnArgs(); +} + +/* +================ +hhAISpawnCase::CreateEntSpawnArgs +================ +*/ +void hhAISpawnCase::CreateEntSpawnArgs( void ) { + // Create list of spawn args, copied to encasedAI in Event_CreateAI + entSpawnArgs.Clear(); + idStr tmpStr, realKeyName; + const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL); + while(kv) { + tmpStr = kv->GetKey(); + int usIndex = tmpStr.FindChar("ent_", '_'); + realKeyName = tmpStr.Mid(usIndex+1, strlen( kv->GetKey())-usIndex-1); + entSpawnArgs.Set(realKeyName, kv->GetValue()); + kv = spawnArgs.MatchPrefix("ent_", kv); + } +} diff --git a/neo/prey/Prey/ai_spawncase.h b/neo/prey/Prey/ai_spawncase.h new file mode 100644 index 00000000..3b0688fc --- /dev/null +++ b/neo/prey/Prey/ai_spawncase.h @@ -0,0 +1,34 @@ +#ifndef ai_spawncase_H +#define ai_spawncase_H + +// +// hhAISpawnCase +// +// Spawns a specific monster, attaches to model - waits for trigger before releasing monster +// +class hhAISpawnCase : public hhAnimated { + +public: + CLASS_PROTOTYPE(hhAISpawnCase); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Event_Trigger( idEntity *activator ); + virtual void Event_AnimDone( int animIndex ); + +protected: + void Event_CreateAI( void ); + void Event_AutoTrigger( void ); + + void CreateEntSpawnArgs( void ); + + idEntityPtr encasedAI; // The AI that is currently encased and waiting to come out + bool triggerToggle; // Toggles back and forth to indicated which anim to play (open or close) + int aiSpawnCount; // Current number of monsters spawned + bool waitingForAutoTrigger; // True if we are waiting for an auto-trigger msg to come in (from auto_retrigger_delay setting) + int triggerQueue; // Number of triggers that have occurred during the delay period that must be re-sent + idDict entSpawnArgs; // Spawn args to copy to spawned entities +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_speech.cpp b/neo/prey/Prey/ai_speech.cpp new file mode 100644 index 00000000..2aa92217 --- /dev/null +++ b/neo/prey/Prey/ai_speech.cpp @@ -0,0 +1,403 @@ +// +// ai_speech.cpp +// +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +extern idCVar ai_skipSpeech; + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +////////////////////////////////////////////////////////////////////// +// +// hhAISpeech +// +////////////////////////////////////////////////////////////////////// + +// jrm: none yet + + + +////////////////////////////////////////////////////////////////////// +// +// hhAISpeechHandler +// +////////////////////////////////////////////////////////////////////// + +float hhAISpeechHandler::TokenMatchWeight[] = {3.0f, 2.0f, 1.0f}; +float hhAISpeechHandler::TokenWildcardMatchWeight[] = {1.5f, 1.0f, 0.5f}; +#define TokenWildCard "*" +#define SpeechShaderPrefix "snd_speech_" // IF THIS CHANGES YOU MUST CHANGE THE LENGTH DEFINE! +#define SpeechShaderPrefixLen 11 +#define FreqToken ':' +#define FreqTokenStr ":" + +// +// Construction +// +hhAISpeechHandler::hhAISpeechHandler() +{ + firstSpeech = NULL; + lastSpeech = NULL; + nextSpeechTime = 0; +}; + +// +// Update() +// +void hhAISpeechHandler::Update() +{ + hhAISpeech *currSpeech = firstSpeech; + hhAISpeech *nextSpeech = NULL; + + if(ai_skipSpeech.GetBool()) + return; + + //for( currSpeech = speechQueue.Next(); currSpeech != NULL; currSpeech = nextSpeech ) + while(currSpeech != NULL) + { + // We can't play any more speech yet! + if(gameLocal.GetTime() < nextSpeechTime) + return; + + nextSpeech = currSpeech->next; + + // Has this speech expired? + if(gameLocal.GetTime() > currSpeech->expireTime) + { + RemoveSpeechFromQueue(currSpeech); + currSpeech = nextSpeech; + continue; + } + + // Play the speech! + currSpeech->speaker->StartSound(currSpeech->snd, SND_CHANNEL_VOICE, 0, true, NULL); + + // Debug Start + /* + idStr speechStr; + sprintf(speechStr, "\"%s\"", (const char*)currSpeech->snd); + speechStr.ToLower(); + gameRenderWorld->DrawText(speechStr, currSpeech->speaker->GetOrigin() + idVec3(0.0f, 0.0f, 12.0f), 0.5f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(),1,1000); + gameLocal.Printf("\n%.01f, %s says %s", (float)MS2SEC(gameLocal.GetTime()), (const char*)currSpeech->speaker->name, (const char*)speechStr); + */ + // Debug End + + RemoveSpeechFromQueue(currSpeech); + nextSpeechTime = gameLocal.GetTime() + MinSpeechWaitTime; + + currSpeech = nextSpeech; + } + + + +}; + +// +// PlaySpeech() +// +bool hhAISpeechHandler::PlaySpeech(idAI *source, idStr sndShd, int expireDelay, bool allowQueue) +{ + if(ai_skipSpeech.GetBool()) + return FALSE; + + if(!allowQueue) { + // just play it and return + HH_ASSERT(FALSE); // Code not done yet! + return TRUE; + } + + // Do we have more slots availble? + hhAISpeech *freeSpeech = GetFreeSpeech(); + if(!freeSpeech) + return FALSE; + + // Setup the new speech + freeSpeech->speaker = source; + freeSpeech->expireTime = gameLocal.GetTime() + expireDelay; + freeSpeech->snd = sndShd; + AddSpeechToQueue(freeSpeech); + return TRUE; +}; + +// +// ClearSpeech() +// +void hhAISpeechHandler::ClearSpeech() +{ + firstSpeech = NULL; + lastSpeech = NULL; + + for(int i=0;inext = NULL; + speech->prev = NULL; + firstSpeech = speech; + lastSpeech = speech; + return; + } + + // Add it to the end of the list + HH_ASSERT(lastSpeech && lastSpeech->next == NULL); + lastSpeech->next = speech; + speech->prev = lastSpeech; + speech->next = NULL; + lastSpeech = speech; +} + +// +// RemoveSpeechFromQueue() +// +void hhAISpeechHandler::RemoveSpeechFromQueue(hhAISpeech *speech) +{ + speech->Clear(); + + // Remove from one of the ends? + if(speech == firstSpeech || speech == lastSpeech) { + if(speech == firstSpeech) { + firstSpeech = firstSpeech->next; + if(firstSpeech) + firstSpeech->prev = NULL; + } + + if(speech == lastSpeech) { + lastSpeech = lastSpeech->prev; + if(lastSpeech) + lastSpeech->next = NULL; + } + return; + } + + + // Middle? + if(speech->next) + speech->next->prev = speech->prev; + if(speech->prev) + speech->prev->next = speech->next; +} + +// +// GetSpeechFrequency() +// +float hhAISpeechHandler::GetSpeechFrequency(idStr sndShader) +{ + if(ai_skipSpeech.GetBool()) + return 0.0f; + + int firstBreak = sndShader.Find(FreqToken); + if(firstBreak == -1) + return 1.0f; + + int secondBreak = sndShader.Find(FreqTokenStr, true, firstBreak); + if(secondBreak == -1) + secondBreak = sndShader.Length(); + + idStr freqStr = sndShader.Mid(firstBreak, secondBreak - firstBreak); + + int f = atoi((const char*)freqStr); + return idMath::ClampFloat(0.0f, 1.0f, (float(f) * 0.01f)); +}; + +// +// GetSpeechResponseDelay() +// +int hhAISpeechHandler::GetSpeechResponseDelay(idStr sndShader) +{ + // TODO: Fill this code in when we actually need it. Does 1 second work all the time? + + return 500; +}; + +// +// GetSpeechSoundShader() +// +bool hhAISpeechHandler::GetSpeechSoundShader(idAI *source, idStr speechDesc, idStr &outClosestSndShd) +{ + HH_ASSERT(source != NULL); + + const idKeyValue *kv = NULL; + + if(ai_skipSpeech.GetBool()) + return FALSE; + + // Quick out - do we have this EXACT snd shader? + idStr sndShader = SpeechShaderPrefix + speechDesc; + kv = source->spawnArgs.MatchPrefix(sndShader); + if(kv) { + outClosestSndShd = kv->GetKey(); + return TRUE; + } + + // Extrace the tokens for our 'desired' speech pattern + SpeechTokens desired; + if(!ExtractSpeechTokens(speechDesc, desired)) + return FALSE; + + // Find our best potential speech pattern match + float bestRating = 0.0f; + float currRating = -1.0f; + const idKeyValue *bestKey = NULL; + SpeechTokens currPotential; + + kv = source->spawnArgs.MatchPrefix(SpeechShaderPrefix); + + while(kv) + { + idStr speechStr; + speechStr = kv->GetKey().Mid(SpeechShaderPrefixLen, kv->GetKey().Length() - SpeechShaderPrefixLen); + + if(!ExtractSpeechTokens(speechStr, currPotential)) + currRating = -1.0f; + else + currRating = GetMatchRating(desired, currPotential); + + if(currRating > bestRating) + { + bestRating = currRating; + bestKey = kv; + } + + kv = source->spawnArgs.MatchPrefix(SpeechShaderPrefix,kv); + } + + // We've found a match to some degree + if(bestKey && bestRating > 0.0f) + { + outClosestSndShd = bestKey->GetKey(); + return TRUE; + } + + return FALSE; +}; + +// +// GetMatchRating() +// +float hhAISpeechHandler::GetMatchRating(SpeechTokens &desired, SpeechTokens &potential) +{ + float rating = 0.0f; + + for(int i=0;i= 0) + outTokens.tokens[TT_What] = outTokens.tokens[TT_What].Mid(0, outTokens.tokens[TT_What].Length() - (findFreqBreak+1)); + + return true; +}; + +/* +================ +hhAISpeechHandler::Save +================ +*/ +void hhAISpeechHandler::Save(idSaveGame *savefile) const { + for (int i = 0; i < SpeechPoolSize; i++) { + savefile->WriteObject(speechPool[i].speaker); + savefile->WriteString(speechPool[i].snd); + savefile->WriteInt(speechPool[i].expireTime); + savefile->WriteInt(GetPoolIndex(speechPool[i].next)); + savefile->WriteInt(GetPoolIndex(speechPool[i].prev)); + } + + savefile->WriteInt(GetPoolIndex(firstSpeech)); + savefile->WriteInt(GetPoolIndex(lastSpeech)); + savefile->WriteInt(nextSpeechTime); +} + +/* +================ +hhAISpeechHandler::Restore +================ +*/ +void hhAISpeechHandler::Restore(idRestoreGame *savefile) { + int tmp; + for (int i = 0; i < SpeechPoolSize; i++) { + savefile->ReadObject(reinterpret_cast (speechPool[i].speaker)); + savefile->ReadString(speechPool[i].snd); + savefile->ReadInt(speechPool[i].expireTime); + savefile->ReadInt(tmp); + speechPool[i].next = (tmp == -1 ? NULL : &speechPool[tmp]); + savefile->ReadInt(tmp); + speechPool[i].prev = (tmp == -1 ? NULL : &speechPool[tmp]); + } + + savefile->ReadInt(tmp); + firstSpeech = (tmp == -1 ? NULL : &speechPool[tmp]); + savefile->ReadInt(tmp); + lastSpeech = (tmp == -1 ? NULL : &speechPool[tmp]); + savefile->ReadInt(nextSpeechTime); +} + + +int hhAISpeechHandler::GetPoolIndex(hhAISpeech *obj) const { + if (!obj) { + return -1; + } + + for (int i = 0; i < SpeechPoolSize; i++) { + if (obj == &speechPool[i]) { + return i; + } + } + + HH_ASSERT(!"Couldn't find pool index!"); + return -1; +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_speech.h b/neo/prey/Prey/ai_speech.h new file mode 100644 index 00000000..754393c8 --- /dev/null +++ b/neo/prey/Prey/ai_speech.h @@ -0,0 +1,102 @@ +#ifndef __HH_AISPEECH_H +#define __HH_AISPEECH_H + + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// +// hhAISpeech +// +class hhAISpeech +{ +public: + hhAISpeech() {Clear(); next = NULL; prev = NULL; } + + void Clear(void) {speaker = NULL; expireTime = -1;} + bool IsCleared(void) {return speaker == NULL;} // Assumes speech will NEVER come from a 'null' speaker, NULL = empty + + idAI* speaker; + idStr snd; + int expireTime; + + hhAISpeech *next; + hhAISpeech *prev; +}; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +// +// hhAISpeechHandler +// +class hhAISpeechHandler +{ +public: +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + enum + { + SpeechPoolSize = 8, + MinSpeechWaitTime = 2000, // We have to wait at LEAST this long before we can play ANY more speech + }; + + hhAISpeechHandler(); + virtual ~hhAISpeechHandler() { } + virtual void Update(); // note: not named Think() on purpose, because we are NOT an entity + + // Play speech from a given AI - returns true if speech was queued - DOES NOT mean it is guarenteed to play + bool PlaySpeech(idAI *source, idStr sndShader, int expireDelay = 1000, bool allowQueue = true); + + // Finds the matching snd shader to the speechDesc given, as well as 0->1 of the frequency for this speech + // Returns FALSE if no possible match could be found. + bool GetSpeechSoundShader(idAI *source, idStr speechDesc, idStr &outClosestSndShd); + + // From a sound shader name, return the frequency from 0->1 + float GetSpeechFrequency(idStr sndShader); + + // From a sound shader name, return the delay in MS of how long it takes to 'comprehend' the given shader. + // Example: Someone says "Take the shuttle!" and we want the person taking the shuttle to wait at least 1 second before they do it + // so it looks like they comprehended the command + int GetSpeechResponseDelay(idStr sndShader); + + // Remove all queued speech + void ClearSpeech(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + hhAISpeech speechPool[SpeechPoolSize]; // Pool of all possible speech slots + hhAISpeech* GetFreeSpeech(); // Returns the first empty speech obj available in the pool + int ClearExpiredSpeech(); // Clears out any expired speech in the pool + + enum TokenType + { + TT_Desire = 0, // The desire we are going to satisfy + TT_How, // How we are going to satisfy it + TT_What, // What entity we will use to satisfy it + + TT_Total, + }; + + struct SpeechTokens + { + idStr tokens[TT_Total]; + }; + + static float TokenMatchWeight[TT_Total]; + static float TokenWildcardMatchWeight[TT_Total]; + + // Returns a 0+ rating of 'how' close the potential speech matches the desired speech + float GetMatchRating(SpeechTokens &desired, SpeechTokens &potential); + bool ExtractSpeechTokens(idStr speechDesc, SpeechTokens &outTokens); + + // because I hate id's linked list + hhAISpeech *firstSpeech; + hhAISpeech *lastSpeech; + void AddSpeechToQueue(hhAISpeech *speech); + void RemoveSpeechFromQueue(hhAISpeech *speech); + int GetPoolIndex(hhAISpeech *obj) const; + + int nextSpeechTime; // The time we are next allowed to play speech +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/ai_sphereboss.cpp b/neo/prey/Prey/ai_sphereboss.cpp new file mode 100644 index 00000000..df216f76 --- /dev/null +++ b/neo/prey/Prey/ai_sphereboss.cpp @@ -0,0 +1,533 @@ + + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_UpdateTarget( "" ); +const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v" ); +const idEventDef MA_GetCircleNode("getCircleNode", NULL, 'e' ); +const idEventDef MA_SpinClouds("spinClouds", "f" ); +const idEventDef MA_SetSeekScale("setSeekScale", "f" ); + +CLASS_DECLARATION(hhMonsterAI, hhSphereBoss) + EVENT( EV_UpdateTarget, hhSphereBoss::Event_UpdateTarget ) + EVENT( EV_DirectMoveToPosition, hhSphereBoss::Event_DirectMoveToPosition ) + EVENT( MA_GetCircleNode, hhSphereBoss::Event_GetCircleNode ) + EVENT( MA_SpinClouds, hhSphereBoss::Event_SpinClouds ) + EVENT( MA_SetSeekScale, hhSphereBoss::Event_SetSeekScale ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +void hhSphereBoss::Spawn() { + for ( int i=0;i<10;i++ ) { + lastTargetPos.Append( vec3_zero ); + } + nextShieldImpact = 0; + lastNodeIndex = -1; + PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) ); +} + +void hhSphereBoss::Event_UpdateTarget() { + for ( int i=0;i<10;i++ ) { + if ( i == 9 ) { + if ( enemy.IsValid() ) { + lastTargetPos[i] = enemy->GetOrigin(); + } else { + lastTargetPos[i] = lastTargetPos[i - 1]; + } + } else { + lastTargetPos[i] = lastTargetPos[i + 1]; + } + } + PostEventSec( &EV_UpdateTarget, spawnArgs.GetFloat( "target_period", "0.1" ) ); +} + +//overridden to allow custom accuracy/numbers per projectile def +idProjectile *hhSphereBoss::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { + idVec3 muzzle; + idVec3 dir; + idVec3 start; + trace_t tr; + idBounds projBounds; + float distance; + const idClipModel *projClip; + float attack_accuracy; + float attack_cone; + float projectile_spread; + float diff; + float angle; + float spin; + idAngles ang; + int num_projectiles; + int i; + idMat3 axis; + idVec3 tmp; + idProjectile *lastProjectile; + + //HUMANHEAD mdc - added to support multiple projectiles + if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile + int projIndex = FindProjectileInfo( desiredProjectileDef ); + if( projIndex >= 0 ) { + SetCurrentProjectile( projIndex ); + } + } + //HUMANHEAD END + + + if ( !projectileDef ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + if ( projectileDef->GetFloat( "attack_accuracy" ) ) { + attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" ); + } else { + attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" ); + } + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + if ( projectileDef->GetFloat( "projectile_spread" ) ) { + projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" ); + } else { + projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" ); + } + if ( projectileDef->GetFloat( "num_projectiles" ) ) { + num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" ); + } else { + num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" ); + } + + GetMuzzle( jointname, muzzle, axis ); + + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, axis[ 0 ] ); + } + + lastProjectile = projectile.GetEntity(); + + if ( target != NULL ) { + tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; + tmp.Normalize(); + axis = tmp.ToMat3(); + } else { + axis = viewAxis; + } + + // rotate it because the cone points up by default + tmp = axis[2]; + axis[2] = axis[0]; + axis[0] = -tmp; + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = lastProjectile->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( axis ); + + // 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( muzzle, viewAxis[ 0 ], distance ) ) { + start = muzzle + 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(); + } + + gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); + muzzle = tr.endpos; + + // set aiming direction + if ( spawnArgs.GetBool( "lag_target", "0" ) ) { + dir = (lastTargetPos[0] - muzzle).ToNormal(); + } else { + GetAimDir( muzzle, target, this, dir ); + } + ang = dir.ToAngles(); + + // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. + float t = MS2SEC( gameLocal.time + entityNumber * 497 ); + ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; + ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; + + if ( clampToAttackCone ) { + // 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, current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = current_yaw - attack_cone; + } + } + + axis = ang.ToMat3(); + + float spreadRad = DEG2RAD( projectile_spread ); + for( i = 0; i < num_projectiles; i++ ) { + // spread the projectiles out + angle = idMath::Sin( spreadRad * 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(); + + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( muzzle, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzle, dir, vec3_origin ); + projectile = NULL; + } + + TriggerWeaponEffects( muzzle,axis ); + + lastAttackTime = gameLocal.time; + +//HUMANHEAD mdc - added to support multiple projectiles + projectile = NULL; + SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side +//HUMANHEAD END + + return lastProjectile; +} + +void hhSphereBoss::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + HandleNoGore(); + if ( !AI_DEAD ) { + AI_DEAD = true; + state = GetScriptFunction( "state_SphereDeath" ); + SetState( state ); + SetWaitState( "" ); + } +} + +void hhSphereBoss::FlyTurn( void ) { + if ( AI_FACE_ENEMY ) { + TurnToward( enemy->GetOrigin() ); + } else { + if ( move.moveCommand == MOVE_FACE_ENEMY ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + } + Turn(); +} + +void hhSphereBoss::Event_SetSeekScale( float new_scale ) { + fly_seek_scale = new_scale; +} + +void hhSphereBoss::Event_GetCircleNode() { + if ( !enemy.IsValid() ) { + idThread::ReturnEntity( NULL ); + return; + } + idEntity *ent = NULL; + idEntity *bestEnt = NULL; + lastNodeIndex++; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->IsType( hhAINode::Type ) ) { + continue; + } + if ( !ent->spawnArgs.GetBool( "circle_node" ) ) { + continue; + } + if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) { + bestEnt = ent; + break; + } + } + if ( !bestEnt ) { + lastNodeIndex = 0; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->IsType( hhAINode::Type ) ) { + continue; + } + if ( !ent->spawnArgs.GetBool( "circle_node" ) ) { + continue; + } + if ( lastNodeIndex == ent->spawnArgs.GetInt( "circle_node_index" ) ) { + bestEnt = ent; + break; + } + } + } + if ( bestEnt ) { + idThread::ReturnEntity( bestEnt ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhSphereBoss::Event_GetCombatNode() { + idEntity *ent = NULL; + float bestDist = 0.0f; + float dist; + idEntity *bestEnt = NULL; + idList list; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent || !ent->spawnArgs.GetBool( "ainode" ) ) { + continue; + } + dist = (ent->GetOrigin() - GetOrigin()).Length(); + if ( dist < spawnArgs.GetFloat( "node_dist", "400" ) ) { + continue; + } + list.Append( ent ); + } + if ( list.Num() > 0 ) { + idThread::ReturnEntity( list[gameLocal.random.RandomInt(list.Num())] ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhSphereBoss::Event_DirectMoveToPosition(const idVec3 &pos) { + StopMove(MOVE_STATUS_DONE); + DirectMoveToPosition(pos); +} + +void hhSphereBoss::FlyMove( void ) { + idVec3 goalPos; + idVec3 oldorigin; + idVec3 newDest; + + AI_BLOCKED = false; + if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { + if ( AI_FACE_ENEMY ) { + StopMove( MOVE_STATUS_DONE ); + } else { + AI_MOVE_DONE = true; + } + } + + idVec3 vel = physicsObj.GetLinearVelocity(); + goalPos = move.moveDest; + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( move.speed ) { + FlySeekGoal( vel, goalPos ); + } + AddFlyBob( vel ); + AdjustFlySpeed( vel ); + 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( disableGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + AI_BLOCKED = true; + } + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + 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, goalPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhSphereBoss::AdjustFlySpeed( idVec3 &vel ) { + float speed; + + // apply dampening + float damp = spawnArgs.GetFloat( "fly_dampening", "0.01" ); + vel -= vel * damp * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if ( move.speed && ( speed > move.speed ) ) { + speed = move.speed; + } + + vel *= speed; +} + +bool hhSphereBoss::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const { + if ( move.moveType == MOVETYPE_SLIDE ) { + idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } else { + if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) { + if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { + return true; + } + } else { + idBounds bnds( idVec3( -64.0, -64.0f, -64.0f ), idVec3( 64.0, 64.0f, 64.0f ) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + if ( bnds.ContainsPoint( pos ) ) { + return true; + } + } + } + return false; +} + +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhSphereBoss::LinkScriptVariables() { + hhMonsterAI::LinkScriptVariables(); + LinkScriptVariable( AI_FACE_ENEMY ); + LinkScriptVariable( AI_CAN_DAMAGE ); +} + +void hhSphereBoss::Event_SpinClouds( float shouldSpin ) { + if ( shouldSpin == 0.0f ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), ent->spawnArgs.GetFloat( "shaderparm4", "0" ) ); + } + kv = spawnArgs.MatchPrefix( "cloud", kv ); + } + } else { + const idKeyValue *kv = spawnArgs.MatchPrefix( "cloud" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->SetShaderParm( spawnArgs.GetInt( "spin_parm", "4" ), spawnArgs.GetFloat( "spin_value", "0.5" ) ); + } + kv = spawnArgs.MatchPrefix( "cloud", kv ); + } + } +} + +void hhSphereBoss::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + //overridden to allow custom wound code + if ( !AI_CAN_DAMAGE && inflictor && spawnArgs.MatchPrefix("mineDamage") ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + if ( gameLocal.time >= nextShieldImpact ) { + nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); + idVec3 offset; + if ( attacker && inflictor ) { + offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" ); + const char *defName = spawnArgs.GetString( "fx_shield_impact" ); + idEntityFx *impactFx = SpawnFxLocal( defName, inflictor->GetOrigin() + offset, mat3_identity, &fxInfo, gameLocal.isClient ); + if ( impactFx ) { + impactFx->Bind( this, true ); + } + } + } + StartSound( "snd_shield_impact", SND_CHANNEL_BODY ); + if ( inflictor && inflictor->IsType( idProjectile::Type ) ) { + inflictor->PostEventMS( &EV_Remove, 0 ); + } + } else { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + if ( gameLocal.time >= nextShieldImpact ) { + nextShieldImpact = gameLocal.time + int(spawnArgs.GetFloat( "shield_impact_freq", "0.4" ) * 1000); + idVec3 offset; + if ( attacker && inflictor ) { + offset = (attacker->GetOrigin() - inflictor->GetOrigin()).ToNormal() * spawnArgs.GetFloat( "shield_impact_offset", "150" ); + BroadcastFxInfoPrefixed( "fx_pain_impact", inflictor->GetOrigin() + offset, mat3_identity, &fxInfo ); + } + } + } + + bool mine_damage = false; + if ( !AI_CAN_DAMAGE && spawnArgs.MatchPrefix("mineDamage") != NULL ) { + const idKeyValue *kv = spawnArgs.MatchPrefix("mineDamage"); + while( kv && kv->GetValue().Length() ) { + if ( !kv->GetValue().Icmp(damageDefName) ) { + mine_damage = true; + break; + } + kv = spawnArgs.MatchPrefix("mineDamage", kv); + } + if (!mine_damage) { + return; + } + } + + if ( !mine_damage ) { + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } + + if ( !AI_CAN_DAMAGE && health > 0 ) { + SetState( GetScriptFunction( "state_Pain" ) ); + SetWaitState( "" ); + } +} + +/* +===================== +hhSphereBoss::Save +===================== +*/ +void hhSphereBoss::Save( idSaveGame *savefile ) const { + savefile->WriteInt( lastTargetPos.Num() ); + savefile->WriteInt( nextShieldImpact ); + for ( int i=0;iWriteVec3( lastTargetPos[i] ); + } +} + +/* +===================== +hhSphereBoss::Restore +===================== +*/ +void hhSphereBoss::Restore( idRestoreGame *savefile ) { + int num = 0; + savefile->ReadInt( num ); + savefile->ReadInt( nextShieldImpact ); + lastTargetPos.SetNum( num ); + for ( int i=0;iReadVec3( lastTargetPos[i] ); + } +} + +void hhSphereBoss::AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ) { + //overridden to remove woundmanager hook and use custom wound code is Damage() + if ( AI_CAN_DAMAGE ) { + return hhMonsterAI::AddLocalMatterWound( jointNum, localOrigin, localNormal, localDir, damageDefIndex, collisionMaterial ); + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/ai_sphereboss.h b/neo/prey/Prey/ai_sphereboss.h new file mode 100644 index 00000000..db175af4 --- /dev/null +++ b/neo/prey/Prey/ai_sphereboss.h @@ -0,0 +1,45 @@ + +#ifndef __PREY_AI_SPHEREBOSS_H__ +#define __PREY_AI_SPHEREBOSS_H__ + +class hhSphereBoss : public hhMonsterAI { + +public: + CLASS_PROTOTYPE(hhSphereBoss); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_UpdateTarget() {}; + void Event_GetCombatNode() {}; + void Event_GetCircleNode() {}; + void Event_DirectMoveToPosition(const idVec3 &pos) {}; + void Event_SpinClouds( float shouldSpin ) {}; + void Event_SetSeekScale( float new_scale ) {}; +#else + void Spawn(); + void FlyTurn(); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ); + void AdjustFlySpeed( idVec3 &vel ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void FlyMove( void ); + bool ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const; + void LinkScriptVariables(); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ); + + void Event_UpdateTarget(); + void Event_GetCombatNode(); + void Event_GetCircleNode(); + void Event_DirectMoveToPosition(const idVec3 &pos); + void Event_SpinClouds( float shouldSpin ); + void Event_SetSeekScale( float new_scale ); +protected: + idScriptBool AI_FACE_ENEMY; + idScriptBool AI_CAN_DAMAGE; + idList lastTargetPos; + int lastNodeIndex; + int nextShieldImpact; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/anim_baseanim.cpp b/neo/prey/Prey/anim_baseanim.cpp new file mode 100644 index 00000000..bd08a574 --- /dev/null +++ b/neo/prey/Prey/anim_baseanim.cpp @@ -0,0 +1,104 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" +#include "../../renderer/Model_local.h" // Hackish. Needed to access certain classes. Imitates game/anim/Blend.cpp +/* +=============== +hhMD5Anim::hhMD5Anim +=============== +*/ +hhMD5Anim::hhMD5Anim(void) { + + start = 0.0f; + end = 1.0f; +} + + +/* +============================== +hhMD5Anim::setLimits + Set the limits of the animation to play. + start and end should vary from 0 to 1 +============================== +*/ +void hhMD5Anim::SetLimits(float start, float end) const { + + if (start <= end) { + this->start = start; + this->end = end; + } + else { + this->start = end; + this->end = start; + } + +} + + +/* +============================== +hhMD5Anim::ConvertTimeToFrame +============================== +*/ +void hhMD5Anim::ConvertTimeToFrame( int time, int cycleCount, + frameBlend_t &frame ) const { + float newTime; + float timeOffset; + + + timeOffset = animLength * start; + + if (time < 0) { + newTime = timeOffset; + } + else { + newTime = time + timeOffset; + } + + idMD5Anim::ConvertTimeToFrame(newTime, cycleCount, frame); + + +} + + +/* +============================== +hhMD5Anim::Length +============================== +*/ +int hhMD5Anim::Length(void) const { + int intLength; + + + intLength = animLength * (end - start); + if ( ( intLength == 0 ) && ( end != start ) ) { + intLength = 1; + } + + + return( intLength ); + +} + +/* +================ +hhMD5Anim::Save +================ +*/ +void hhMD5Anim::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( start ); + savefile->WriteFloat( end ); +} + +/* +================ +hhMD5Anim::Restore +================ +*/ +void hhMD5Anim::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( start ); + savefile->ReadFloat( end ); +} + diff --git a/neo/prey/Prey/anim_baseanim.h b/neo/prey/Prey/anim_baseanim.h new file mode 100644 index 00000000..b8bce2ee --- /dev/null +++ b/neo/prey/Prey/anim_baseanim.h @@ -0,0 +1,29 @@ + +#ifndef __PREY_ANIM_MD5ANIM_H__ +#define __PREY_ANIM_MD5ANIM_H__ + +// Forward declar +class idMD5Bone; + +class hhMD5Anim : public idMD5Anim { + +public: + + hhMD5Anim(void); + void SetLimits(float start, float end) const; + virtual void ConvertTimeToFrame( int time, int cycleCount, frameBlend_t &frame ) const; + virtual int Length( void ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + mutable float start; + mutable float end; + + +}; + + +#endif /* __PREY_ANIM_MD5ANIM_H__ */ + diff --git a/neo/prey/Prey/force_converge.cpp b/neo/prey/Prey/force_converge.cpp new file mode 100644 index 00000000..0d97a582 --- /dev/null +++ b/neo/prey/Prey/force_converge.cpp @@ -0,0 +1,173 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idForce, hhForce_Converge ) +END_CLASS + + +hhForce_Converge::hhForce_Converge( void ) { + restoreFactor = 1.0f; + restoreForceSlack = 1.0f; + restoreTime = 0.1f; + ent = NULL; + physics = NULL; + axisEnt = NULL; + bShuttle = false; +} + +hhForce_Converge::~hhForce_Converge( void ) { +} + +void hhForce_Converge::Save(idSaveGame *savefile) const { + ent.Save(savefile); + axisEnt.Save(savefile); + savefile->WriteVec3( target ); + savefile->WriteVec3( offset ); + savefile->WriteInt( bodyID ); + savefile->WriteFloat( restoreTime ); + savefile->WriteFloat( restoreFactor ); + savefile->WriteFloat( restoreForceSlack ); + savefile->WriteBool( bShuttle ); +} + +void hhForce_Converge::Restore( idRestoreGame *savefile ) { + ent.Restore(savefile); + axisEnt.Restore(savefile); + savefile->ReadVec3( target ); + savefile->ReadVec3( offset ); + savefile->ReadInt( bodyID ); + savefile->ReadFloat( restoreTime ); + savefile->ReadFloat( restoreFactor ); + savefile->ReadFloat( restoreForceSlack ); + savefile->ReadBool( bShuttle ); + + physics = NULL; // HUMANHEAD mdl: Updated each frame +} + +void hhForce_Converge::Evaluate( int time ) { + + // Get the physics of ent each frame, in case their physics object was changed. + physics = ent.IsValid() ? ent->GetPhysics() : NULL; + + // Apply convergent force + if (physics) { + idVec3 p = physics->GetOrigin( bodyID ) + offset * physics->GetAxis( bodyID ); + idVec3 x = p - target; + + if (axisEnt.IsValid()) { + idBounds bounds = ent->GetPhysics()->GetBounds(); + idVec3 rightPt = ent->GetOrigin() + (ent->GetAxis()[0] * bounds[0].x); + idVec3 axisRightPt = axisEnt->GetOrigin() + (axisEnt->GetAxis()[1] * bounds[1].x); + + idVec3 leftPt = ent->GetOrigin() + (ent->GetAxis()[0] * bounds[1].x); + idVec3 axisLeftPt = axisEnt->GetOrigin() + (axisEnt->GetAxis()[1] * bounds[0].x); + + //gameRenderWorld->DebugLine(idVec4(1, 0, 0, 1), rightPt, axisRightPt, 100); + + p = rightPt; + x = p - axisRightPt; + + x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point + + idVec3 v = physics->GetLinearVelocity(); + float m = physics->GetMass(); + float b = idMath::Sqrt(4*restoreFactor*m); + idVec3 force = -restoreFactor*x - b*v; // use with addforce, k=200000 + physics->AddForce(bodyID, p, force * 0.5f ); // Great to show mass variations + + //gameRenderWorld->DebugLine(idVec4(1, 0, 0, 1), leftPt, axisLeftPt, 100); + + p = leftPt; + x = p - axisLeftPt; + + x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point + + force = axisLeftPt - leftPt; + force *= m * 4; + physics->AddForce(bodyID, p, force ); // Great to show mass variations + return; + } + + x *= restoreForceSlack; //allow a linear buildup based on the distance of the target point + + if (ent->IsType(idAFEntity_Base::Type)) { // Actors use velocity method since their masses are unrealistic, ragdolls need it too + if (!ent->fl.tooHeavyForTractor) { + // Velocity method: cover distance in fixed time + idVec3 velocity = -x / restoreTime; // Use with SetLinearVelocity() + if (ent->IsType(idActor::Type)) { //if a player is affected by this force.. + //..do not allow him to build up velocity into his ground plane + if (ent->GetPhysics()->IsType(idPhysics_Player::Type) && ent->GetPhysics()->HasGroundContacts()) { + velocity.x *= -ent->GetPhysics()->GetGravityNormal().x; + velocity.y *= -ent->GetPhysics()->GetGravityNormal().y; + velocity.z *= -ent->GetPhysics()->GetGravityNormal().z; + } + } + physics->SetLinearVelocity(velocity, bodyID); + } + else { + // Entities that are too heavy get no force, just give feedback force + } + } else { + float m = physics->GetMass(); + if (bShuttle && m < 1500.0f) { // Different equation for picking up low mass objects with the shuttle + idVec3 v = physics->GetLinearVelocity(); + float springFactor = restoreFactor*0.0002f; + float dampFactor = idMath::Sqrt(springFactor); + idVec3 force = (-x * springFactor * m) - (v * dampFactor * m); + physics->AddForce(bodyID, p, force ); + } else { // Critically Damped spring: m*a = -k*x - b*v ; b = 2 * sqrt(m*k) + idVec3 v = physics->GetLinearVelocity(); + float m = physics->GetMass(); + float b = idMath::Sqrt(4.0f*restoreFactor*m); + idVec3 force = -restoreFactor*x - b*v; // use with addforce, k=200000 + physics->AddForce(bodyID, p, force ); // Great to show mass variations + } + } +/* else { // Impulse method + idVec3 v = physics->GetLinearVelocity(); + float b = idMath::Sqrt(4*restoreFactor); + impulse = -restoreFactor*x - b*v; // Use with applyimpulse, k=50000 + physics->ApplyImpulse(bodyID, p, impulse ); // Worked for all but some moveables (k=50000) + } + else { // Mass scaled force method, applies varying force to keep constant velocity + idVec3 v = physics->GetLinearVelocity(); + float m = physics->GetMass(); + float b = idMath::Sqrt(4*restoreFactor); + impulse = (-restoreFactor*x - b*v) * m; // use with addforce, k=500 + physics->AddForce(bodyID, p, impulse ); // Works for all but ragdolls (k=500) + } + }*/ + } +} + +void hhForce_Converge::RemovePhysics( const idPhysics *phys ) { + // physics of ent is stored each evaluate so we can compare pointers directly, rather than querying + // ent about it's physics. This is done because typically, physics objects are reported as removed + // during deconstruction of the entities. + if (ent.IsValid() && phys == physics) { + SetEntity(NULL); + } +} + +void hhForce_Converge::SetRestoreTime( float time ) { + restoreTime = idMath::ClampFloat(0.02f, time, time); +} + +void hhForce_Converge::SetTarget(idVec3 &newTarget) { + target = newTarget; +} + +void hhForce_Converge::SetEntity(idEntity *entity, int id, const idVec3 &point) { + ent = entity; + bodyID = id; + offset = point; + physics = NULL; +} + +void hhForce_Converge::SetAxisEntity(idEntity *entity) { + axisEnt = entity; +} + diff --git a/neo/prey/Prey/force_converge.h b/neo/prey/Prey/force_converge.h new file mode 100644 index 00000000..def1f9af --- /dev/null +++ b/neo/prey/Prey/force_converge.h @@ -0,0 +1,44 @@ +#ifndef __FORCE_CONVERGE_H__ +#define __FORCE_CONVERGE_H__ + +//=============================================================================== +// +// Convergent force +// +//=============================================================================== + +class hhForce_Converge : public idForce { + CLASS_PROTOTYPE( hhForce_Converge ); + +public: + hhForce_Converge( void ); + virtual ~hhForce_Converge( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SetTarget(idVec3 &newTarget); + void SetEntity(idEntity *entity, int id=0, const idVec3 &point=vec3_origin); + idEntity * GetEntity() const { return ent.GetEntity(); } + void SetRestoreTime( float time ); + void SetRestoreFactor( float factor ){ restoreFactor = factor; } + void SetRestoreSlack(float slack) { restoreForceSlack = slack; } + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + void SetAxisEntity(idEntity *entity); + void SetShuttle(bool shuttle) { bShuttle = shuttle; } + +private: + idEntityPtr ent; // Entity to apply forces to + idEntityPtr axisEnt; + idPhysics * physics; // physics object of entity during last evaluate + idVec3 target; // Point of convergence + idVec3 offset; // Entity local offset for force application + int bodyID; // Body ID for force application + float restoreTime; // Ideal convergence time + float restoreFactor; // Spring constant for restorative force + float restoreForceSlack; //rww - allow a linear buildup based on the distance of the target point + bool bShuttle; //mdl: Use an alternate spring equation for shuttle tractor beam +}; + +#endif diff --git a/neo/prey/Prey/game_afs.cpp b/neo/prey/Prey/game_afs.cpp new file mode 100644 index 00000000..ec48da7c --- /dev/null +++ b/neo/prey/Prey/game_afs.cpp @@ -0,0 +1,100 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/*********************************************************************** + + hhAFEntity + +***********************************************************************/ + +// nla - Added to allow the mappers to do huge translations w/out too much bouncing +const idEventDef EV_EnableRagdoll( "enableRagdoll", "" ); +const idEventDef EV_DisableRagdoll( "disableRagdoll", "" ); + +CLASS_DECLARATION( idAFEntity_Generic, hhAFEntity ) + EVENT( EV_EnableRagdoll, hhAFEntity::Event_EnableRagdoll ) + EVENT( EV_DisableRagdoll, hhAFEntity::Event_DisableRagdoll ) +END_CLASS + +void hhAFEntity::Spawn( void ) { + fl.takedamage = !spawnArgs.GetBool("noDamage"); + if (spawnArgs.FindKey("gravity")) { + PostEventMS(&EV_ResetGravity, 100); // Post after first think, when gravity is reset by UpdateGravity() + } +} + +void hhAFEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) { + return; + } + idAFEntity_Generic::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +//This is so ragdolls shutdown when attached to movers that are outside of the pvs. +void hhAFEntity::DormantBegin() { + idEntity::DormantBegin(); + Event_DisableRagdoll(); +} + +void hhAFEntity::DormantEnd() { + idEntity::DormantEnd(); + Event_EnableRagdoll(); +} + +void hhAFEntity::Event_EnableRagdoll() { + af.GetPhysics()->Thaw(); +} + +void hhAFEntity::Event_DisableRagdoll() { + af.GetPhysics()->Freeze(); +} + + + + +/*********************************************************************** + + hhAFEntity_WithAttachedHead + +***********************************************************************/ + +CLASS_DECLARATION( idAFEntity_WithAttachedHead, hhAFEntity_WithAttachedHead ) + EVENT( EV_EnableRagdoll, hhAFEntity_WithAttachedHead::Event_EnableRagdoll ) + EVENT( EV_DisableRagdoll, hhAFEntity_WithAttachedHead::Event_DisableRagdoll ) +END_CLASS + +void hhAFEntity_WithAttachedHead::Spawn( void ) { + fl.takedamage = !spawnArgs.GetBool("noDamage"); + if (spawnArgs.FindKey("gravity")) { + PostEventMS(&EV_ResetGravity, 100); // Post after first think, when gravity is reset by UpdateGravity() + } +} + +void hhAFEntity_WithAttachedHead::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) { + return; + } + idAFEntity_WithAttachedHead::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +//This is so ragdolls shutdown when attached to movers that are outside of the pvs. +void hhAFEntity_WithAttachedHead::DormantBegin() { + idEntity::DormantBegin(); + Event_DisableRagdoll(); +} + +void hhAFEntity_WithAttachedHead::DormantEnd() { + idEntity::DormantEnd(); + Event_EnableRagdoll(); +} + +void hhAFEntity_WithAttachedHead::Event_EnableRagdoll() { + af.GetPhysics()->Thaw(); +} + +void hhAFEntity_WithAttachedHead::Event_DisableRagdoll() { + af.GetPhysics()->Freeze(); +} diff --git a/neo/prey/Prey/game_afs.h b/neo/prey/Prey/game_afs.h new file mode 100644 index 00000000..a50ef908 --- /dev/null +++ b/neo/prey/Prey/game_afs.h @@ -0,0 +1,49 @@ +#ifndef __GAME_AFS_H__ +#define __GAME_AFS_H__ + +extern const idEventDef EV_CursorDrop; +extern const idEventDef EV_CursorDone; + +/*********************************************************************** + +hhAFEntity + +***********************************************************************/ + +class hhAFEntity : public idAFEntity_Generic { + +public: + CLASS_PROTOTYPE( hhAFEntity ); + + void Spawn(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void DormantBegin(); + virtual void DormantEnd(); + + void Event_EnableRagdoll( void ); + void Event_DisableRagdoll( void ); +}; + + +/*********************************************************************** + +hhAFEntity_WithAttachedHead + +***********************************************************************/ + +class hhAFEntity_WithAttachedHead : public idAFEntity_WithAttachedHead { + +public: + CLASS_PROTOTYPE( hhAFEntity_WithAttachedHead ); + + void Spawn(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void DormantBegin(); + virtual void DormantEnd(); + + void Event_EnableRagdoll(); + void Event_DisableRagdoll(); +}; + + +#endif // __GAME_AFS_H__ diff --git a/neo/prey/Prey/game_alarm.cpp b/neo/prey/Prey/game_alarm.cpp new file mode 100644 index 00000000..819c0f49 --- /dev/null +++ b/neo/prey/Prey/game_alarm.cpp @@ -0,0 +1,76 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhAlarmLight +// +//========================================================================== +CLASS_DECLARATION(idLight, hhAlarmLight) + EVENT(EV_Activate, hhAlarmLight::Event_Activate) +END_CLASS + +void hhAlarmLight::Spawn() { + bAlarmOn = false; + fl.takedamage = false; // Never take damage + + /* Shader parms set up like this (so the editor shows the light being on): + parm6 parm7 + editor(on): 0 0 + spawn(off): 1 0 + trigger(on): 1 1 + broken(off): 1 0 + */ + SetParmState(1.0f, 0.0f); + + // setup the clipModel + GetPhysics()->SetContents( CONTENTS_SOLID ); +} + +void hhAlarmLight::Save(idSaveGame *savefile) const { + savefile->WriteBool( bAlarmOn ); +} + +void hhAlarmLight::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( bAlarmOn ); +} + +void hhAlarmLight::TurnOn() { + if (!bAlarmOn) { + bAlarmOn = true; + + StartSound("snd_alarm", SND_CHANNEL_BODY2, 0, true, NULL); + + SetParmState(1.0f, 1.0f); + } +} + +void hhAlarmLight::TurnOff() { + if (bAlarmOn) { + bAlarmOn = false; + StopSound(SND_CHANNEL_BODY2, true); + + SetParmState(0.0f, 1.0f); + } +} + +void hhAlarmLight::SetParmState(float value6, float value7) { + SetShaderParm(6, value6); + SetShaderParm(7, value7); + SetLightParm(6, value6); + SetLightParm(7, value7); +} + +void hhAlarmLight::Event_Activate(idEntity *activator) { + if (bAlarmOn) { + if (activator && activator->IsType(hhPlayer::Type)) { + TurnOff(); + } + } + else { + TurnOn(); + } +} diff --git a/neo/prey/Prey/game_alarm.h b/neo/prey/Prey/game_alarm.h new file mode 100644 index 00000000..32ee2e0d --- /dev/null +++ b/neo/prey/Prey/game_alarm.h @@ -0,0 +1,21 @@ +#ifndef __GAME_ALARMLIGHT_H__ +#define __GAME_ALARMLIGHT_H__ + +class hhAlarmLight : public idLight { + CLASS_PROTOTYPE( hhAlarmLight ); + +public: + void Spawn(); + void TurnOn(); + void TurnOff(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SetParmState(float value6, float value7); + void Event_Activate(idEntity *activator); + + bool bAlarmOn; +}; + +#endif diff --git a/neo/prey/Prey/game_anim.cpp b/neo/prey/Prey/game_anim.cpp new file mode 100644 index 00000000..e8021641 --- /dev/null +++ b/neo/prey/Prey/game_anim.cpp @@ -0,0 +1,244 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/* +============ +hhAnim::AddFrameCommandExtra +============ +*/ +bool hhAnim::AddFrameCommandExtra( idToken &token, frameCommand_t &fc, idLexer &src, idStr &errorText ) { + errorText = ""; + + + if ( token == "stopSnd" ) { + fc.type = FC_STOPSND; + return( true ); + } else if ( token == "stopSnd_voice" ) { + fc.type = FC_STOPSND_VOICE; + return( true ); + } else if ( token == "stopSnd_voice2" ) { + fc.type = FC_STOPSND_VOICE2; + return( true ); + } else if ( token == "stopSnd_body" ) { + fc.type = FC_STOPSND_BODY; + return( true ); + } else if ( token == "stopSnd_body2" ) { + fc.type = FC_STOPSND_BODY2; + return( true ); + } else if ( token == "stopSnd_body3" ) { + fc.type = FC_STOPSND_BODY3; + return( true ); + } else if ( token == "stopSnd_weapon" ) { + fc.type = FC_STOPSND_WEAPON; + return( true ); + } else if ( token == "stopSnd_item" ) { + fc.type = FC_STOPSND_ITEM; + return( true ); + } else if ( token == "event_args" ) { + fc.type = FC_EVENT_ARGS; + src.ParseRestOfLine( token ); + idStr error = InitFrameCommandEvent( fc, token ); + if( error.Length() ) { + errorText = error.c_str(); + return( false ); + } + + return( true ); + } else if ( token == "launch_missile" ) { + /* HUMANHEAD JRM - Because we parse out the joint when launched + if ( !modelDef->FindJoint( cmd ) ) { + return va( "Joint '%s' not found", cmd.c_str() ); + } + */ + src.ParseRestOfLine( token ); + /* HUMANHEAD JRM - need the rest of the line not just one token + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + */ + fc.type = FC_LAUNCHMISSILE; + fc.string = new idStr( token ); + + return( true ); + } else if( token == "launch_altMissile" ) { + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + fc.type = FC_LAUNCHALTMISSILE; + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "launch_missile_bonedir" ) { + /* HUMANHEAD JRM - we want the whole token + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + */ + src.ParseRestOfLine( token ); + fc.type = FC_LAUNCHMISSILE_BONEDIR; + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "leftfootprint" ) { + fc.type = FC_LEFTFOOTPRINT; + + return( true ); + } else if ( token == "rightfootprint" ) { + fc.type = FC_RIGHTFOOTPRINT; + + return( true ); + } else if ( token == "mood" ) { + fc.type = FC_MOOD; + if( !src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( "" ); + return( true ); + } + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "kick_obstacle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + fc.type = FC_KICK_OBSTACLE; + fc.string = new idStr( token ); + + return( true ); + } else if ( token == "trigger_anim_ent" ) { + /* + if( !src.ReadTokenOnLine( &token ) ) { + errorText = va( "Unexpected end of line" ); + return( true ); + } + */ + fc.type = FC_TRIGGER_ANIM_ENT; + return( true ); + } else if ( token == "set_key" ) { + fc.type = FC_SETKEY; + if( !src.ParseRestOfLine( token ) ) { + errorText = va( "Unexpected end of line" ); + return ( true ); + } + if( !fc.parmList ) { + fc.parmList = new idList; + } + hhUtils::SplitString( token, *fc.parmList, ' ' ); + if( fc.parmList->Num() != 2 ) { + errorText = va( "Invalid number of arguments for setkey frame-command" ); + return ( true ); + } + return ( true ); + } + + + return( false ); +} + +/* +============ +hhAnim::CallFrameCommandsExtra +============ +*/ +bool hhAnim::CallFrameCommandsExtra( const frameCommand_t &command, idEntity *ent ) const { + switch( command.type ) { + case FC_EVENT_ARGS: { + if ( command.function && command.parmList && command.function->eventdef ) { + ent->ProcessEvent( command.function->eventdef, (int)command.parmList ); + } + return( true ); + } + case FC_LAUNCHALTMISSILE: { + //ent->ProcessEvent( &AI_AttackAltMissile, command.string->c_str(), NULL ); + return( true ); + } + case FC_STOPSND: + case FC_STOPSND_VOICE: + case FC_STOPSND_VOICE2: + case FC_STOPSND_BODY: + case FC_STOPSND_BODY2: + case FC_STOPSND_BODY3: + case FC_STOPSND_WEAPON: + case FC_STOPSND_ITEM: { + ent->StopSound( s_channelType(command.type - FC_STOPSND), false ); //rww - do not broadcast + return( true ); + } + case FC_HIDE: { + ent->ProcessEvent( &EV_Hide ); + return( true ); + } + case FC_MOOD: { + if(command.string) { + ent->ProcessEvent( &AI_SetAnimPrefix, command.string->c_str()); + } else { + ent->ProcessEvent( &AI_SetAnimPrefix, " "); + } + return( true ); + } + case FC_LEFTFOOTPRINT: { + ent->ProcessEvent( &EV_FootprintLeft ); + return( true ); + } + case FC_RIGHTFOOTPRINT: { + ent->ProcessEvent( &EV_FootprintRight ); + return( true ); + } + case FC_LAUNCHMISSILE: { // JRM: changed to AttackMissileEx (so we can pass in projectile) + ent->ProcessEvent( &MA_AttackMissileEx, command.string->c_str(),0 ); + return( true ); + } + case FC_LAUNCHMISSILE_BONEDIR: { // JRM: need so we can launch missiles + ent->ProcessEvent( &MA_AttackMissileEx, command.string->c_str(),1 ); + return( true ); + } + case FC_SETKEY: {//mdc: added to be able to set keys from frame commands + if( command.parmList && command.parmList->Num() == 2 ) { + ent->spawnArgs.Set( (*command.parmList)[0].c_str(), (*command.parmList)[1].c_str() ); + } + return ( true ); + } + } + + return( false ); + +} + + +/* +===================== +hhAnim::InitFrameCommandEvent +===================== +*/ +idStr hhAnim::InitFrameCommandEvent( frameCommand_t &command, const idStr& cmdStr ) const { + idStr error; + + if( !command.parmList ) { + command.parmList = new idList; + } + + hhUtils::SplitString( cmdStr, *command.parmList, ' ' ); + + // Find the function + command.function = gameLocal.program.FindFunction( (*command.parmList)[0].c_str(), gameLocal.program.FindType((*command.parmList)[0].c_str()) ); + if( !command.function || !command.function->eventdef || command.function->eventdef->GetNumArgs() != 1 ) { + error = va("Invalid event('%s') for frameCommands event or event_args\n", (*command.parmList)[0].c_str() ); + } + + //Remove function name from the list + command.parmList->RemoveIndex( 0 ); + + //If no parms are needed, no need to waste memory + if( command.parmList->Num() <= 0 ) { + SAFE_DELETE_PTR( command.parmList ); + } + + return error; +} + diff --git a/neo/prey/Prey/game_anim.h b/neo/prey/Prey/game_anim.h new file mode 100644 index 00000000..b0894398 --- /dev/null +++ b/neo/prey/Prey/game_anim.h @@ -0,0 +1,23 @@ + +#ifndef __PREY_GAME_ANIM_H__ +#define __PREY_GAME_ANIM_H__ + +class hhAnim : public idAnim { + +public: + + hhAnim() : idAnim() { exactMatch = false; } + hhAnim( const idDeclModelDef *modelDef, const idAnim *anim ) : idAnim( modelDef, anim ) { } + + virtual bool AddFrameCommandExtra( idToken &token, frameCommand_t &fc, idLexer &src, idStr &errorText ); + virtual bool CallFrameCommandsExtra( const frameCommand_t &command, idEntity *ent ) const; + + bool exactMatch; + +protected: + virtual idStr InitFrameCommandEvent( frameCommand_t &command, const idStr& cmdStr ) const; + +}; + + +#endif /* __PREY_GAME_ANIM_H__ */ diff --git a/neo/prey/Prey/game_animBlend.cpp b/neo/prey/Prey/game_animBlend.cpp new file mode 100644 index 00000000..93770191 --- /dev/null +++ b/neo/prey/Prey/game_animBlend.cpp @@ -0,0 +1,276 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/* +============ +hhAnimBlend::hhAnimBlend +============ +*/ +hhAnimBlend::hhAnimBlend() { + + frozen = false; + freezeStart = -1; + freezeEnd = -1; + freezeCurrent = -1; + rotateTime = -1; + rotateEvent = NULL; + +} + + +/* +===================== +hhAnimBlend::FrameHasChanged +===================== +*/ +bool hhAnimBlend::FrameHasChanged( int currentTime ) const { + + + UpdateFreezeTime( currentTime ); + + if ( frozen ) { + return false; + } + + return( idAnimBlend::FrameHasChanged( currentTime ) ); + +} + + +/* +===================== +hhAnimBlend::AnimTime +===================== +*/ +int hhAnimBlend::AnimTime( int currentTime ) const { + + + if ( animNum ) { + UpdateFreezeTime( currentTime ); + } + + return( idAnimBlend::AnimTime( currentTime ) ); + +} + + +/* +===================== +hhAnimBlend::GetFrameNumber +===================== +*/ +int hhAnimBlend::GetFrameNumber( int currentTime ) const { + + //gameLocal.Printf( "In FRAME NUM\n" ); + + UpdateFreezeTime( currentTime ); + + return( idAnimBlend::GetFrameNumber( currentTime ) ); + +} + + +/* +===================== +hhAnimBlend::CallFrameCommands +===================== +*/ +void hhAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const { + + + if ( frozen ) { + return; + } + + idAnimBlend::CallFrameCommands( ent, fromtime, totime ); + +} + +/* +===================== +hhAnimBlend::BlendAnim +===================== +*/ +bool hhAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const { + + + UpdateFreezeTime( currentTime ); + + return( idAnimBlend::BlendAnim( currentTime, channel, numJoints, blendFrame, blendWeight, removeOriginOffset, overrideBlend, printInfo ) ); + +} + + +/* +===================== +hhAnimBlend::BlendOrigin +===================== +*/ +void hhAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const { + + //gameLocal.Printf( "BLEND ORIGIN\n" ); + + UpdateFreezeTime( currentTime ); + + if ( frozen ) { + return; + } + + idAnimBlend::BlendOrigin( currentTime, blendPos, blendWeight, removeOriginOffset ); + +} + + +/* +===================== +hhAnimBlend::BlendDelta +===================== +*/ +void hhAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const { + + + if ( frozen ) { + return; + } + + idAnimBlend::BlendDelta( fromtime, totime, blendDelta, blendWeight ); + +} + + +/* +===================== +hhAnimBlend::AddBounds +===================== +*/ +bool hhAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const { + + + UpdateFreezeTime( currentTime ); + + if ( frozen ) { + return false; + } + + return( idAnimBlend::AddBounds( currentTime, bounds, removeOriginOffset ) ); + +} + + +/* +=============== +hhAnimBlend::UpdateFreezeTime +=============== +*/ +void hhAnimBlend::UpdateFreezeTime( int currentTime ) const { + + if ( !frozen ) { + return; + } + + if ( currentTime > freezeCurrent ) { + starttime += currentTime - freezeCurrent; + endtime += currentTime - freezeCurrent; + freezeCurrent = currentTime; + } + +} + +extern const idEventDef EV_CheckThaw; + +/* +================ +hhAnimBlend::Freeze + Freeze the animation + Inputs: currentTime - The current time of freeze + freezeEnd - Delta time to end the freeze. Set to -1 if will thaw manually + Outputs: true if frozen successfully, false otherwise +HUMANHEAD nla +================ +*/ +bool hhAnimBlend::Freeze( int currentTime, idEntity *owner, int aFreezeEnd ) { + + if ( frozen ) { + return( false ); + } + + frozen = true; + freezeStart = currentTime; + freezeCurrent = currentTime; + freezeEnd = currentTime + aFreezeEnd; + + // Post the thaw event if time is known + if ( owner ) { + owner->PostEventMS( &EV_CheckThaw, aFreezeEnd ); + } + else { + gameLocal.Warning( "Freeze called on an animator with no owner!" ); + } + + + return( true ); +} + + +/* +=============== +hhAnimBlend::Freeze +HUMANHEAD nla +=============== +*/ +bool hhAnimBlend::Freeze( int currentTime, idEntity *owner ) { + + return( Freeze( currentTime, owner, -1 ) ); +} + + +/* +=============== +hhAnimBlend::Thaw +HUMANHEAD nla +=============== +*/ +bool hhAnimBlend::Thaw( int currentTime ) { + + if ( !frozen ) { + return( false ); + } + + UpdateFreezeTime( currentTime ); + frozen = false; + + + return( true ); +} + + +/* +=============== +hhAnimBlend::ThawIfTime +HUMANHEAD nla +=============== +*/ +bool hhAnimBlend::ThawIfTime( int currentTime ) { + + if ( !frozen ) { + return( false ); + } + + if ( freezeEnd < 0 ) { + return( false ); + } + + if ( currentTime >= freezeEnd ) { + return( Thaw( currentTime ) ); + } + + return( false ); +} + + + + diff --git a/neo/prey/Prey/game_animBlend.h b/neo/prey/Prey/game_animBlend.h new file mode 100644 index 00000000..b809e19e --- /dev/null +++ b/neo/prey/Prey/game_animBlend.h @@ -0,0 +1,6 @@ + +#ifndef __PREY_GAME_ANIMBLEND_H__ +#define __PREY_GAME_ANIMBLEND_H__ + + +#endif /* __PREY_GAME_ANIMBLEND_H__ */ diff --git a/neo/prey/Prey/game_animDriven.cpp b/neo/prey/Prey/game_animDriven.cpp new file mode 100644 index 00000000..e23aa2c7 --- /dev/null +++ b/neo/prey/Prey/game_animDriven.cpp @@ -0,0 +1,287 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_PlayCycle( "", "ds" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhAnimDriven ) + EVENT( EV_PlayCycle, hhAnimDriven::Event_PlayCycle ) +END_CLASS + + +//============== +// hhAnimDriven::Spawn +//============== +void hhAnimDriven::Spawn() { + + passenger = NULL; + hadPassenger = false; + spawnTime = gameLocal.time; + + // We are gonna move the guy manually, turn off having the anim move him + GetAnimator()->RemoveOriginOffset( true ); + + // How do we deal with our owner? + physicsAnim.SetSelf( this ); + + if( spawnArgs.GetBool( "solid", "0" ) ) { + fl.takedamage = true; + physicsAnim.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + physicsAnim.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + GetPhysics()->UnlinkClip(); + } + + + // move up to make sure the monster is at least an epsilon above the floor + physicsAnim.SetOrigin( GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + + SetPhysics( &physicsAnim ); + + //? Have this start at a set time afterwards? + int animLength = PlayAnim( ANIMCHANNEL_ALL, "initial" ); + PostEventMS( &EV_PlayCycle, animLength, ANIMCHANNEL_ALL, "move" ); + + float delta_min = spawnArgs.GetFloat( "delta_scale_min", "1.0" ); + float delta_max = spawnArgs.GetFloat( "delta_scale_max", "1.0" ); + if ( delta_min != 1.0f || delta_max != 1.0f ) { + deltaScale.x = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() ); + deltaScale.y = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() ); + deltaScale.z = idMath::ClampFloat( delta_min, delta_max, gameLocal.random.RandomFloat() ); + } else { + deltaScale = idVec3( 1.0f, 1.0f, 1.0f ); + } + + BecomeActive( TH_TICKER ); +} + +void hhAnimDriven::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( physicsAnim ); + passenger.Save(savefile); + savefile->WriteInt( spawnTime ); + savefile->WriteBool( hadPassenger ); +} + +void hhAnimDriven::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsAnim ); + passenger.Restore(savefile); + savefile->ReadInt( spawnTime ); + savefile->ReadBool( hadPassenger ); +} + + +//============= +// hhAnimDriven::SetPassenger +//============= +void hhAnimDriven::SetPassenger( idEntity *newPassenger, bool orientFromPassenger ) { + idVec3 origin; + idMat3 axis; + + //?! Right now handle only 1 passenger. If we have one, warn and exit. + if ( passenger.IsValid() ) { + const char *oldName = passenger->GetName(); + const char *newName = "NULL"; + + if ( newPassenger ) { + newName = newPassenger->GetName(); + } + gameLocal.Warning( "Error: hhAnimDriven::SetPassenger tried to add passenger %s but already had %s\n", + newName, oldName ); + return; + } + + passenger = newPassenger; + hadPassenger = true; + fl.takedamage = true; + + if ( orientFromPassenger ) { + origin = passenger->GetOrigin(); + axis = passenger->GetAxis(); + } + + GetPhysics()->SetClipModel( new idClipModel( passenger->GetPhysics()->GetClipModel() ), 1.0f ); + GetPhysics()->SetContents( passenger->GetPhysics()->GetContents() ); + GetPhysics()->SetClipMask( passenger->GetPhysics()->GetClipMask() ); + + if ( orientFromPassenger ) { + SetOrigin( origin ); + SetAxis( axis ); + } + + passenger->Bind( this, true ); + +} + + +//============== +// hhAnimDriven::Think +//============== +void hhAnimDriven::Think() { + idVec3 delta; + + // Done move unless we have a passenger. (Give us like 10 secs to get one) + if ( !passenger.IsValid() ) { + // If we had a passenger, they removed themselves, and so should we :) + if ( hadPassenger ) { + PostEventMS( &EV_Remove, 0 ); + } + // If never had a passenger for a second, remove ourselves + else if ( gameLocal.time - spawnTime > 1000 ) { + PostEventMS( &EV_Remove, 0 ); + gameLocal.Warning( "hhAnimDriven %s existed for a second w/out a passenger. Removing.", GetName() ); + } + return; + } + + // Move them based on their anim + GetAnimator()->GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta ); + delta *= GetAxis(); + delta.x *= deltaScale.x; + delta.y *= deltaScale.y; + delta.z *= deltaScale.z; + + physicsAnim.SetDelta( delta ); + + // gameLocal.Printf( "%d Doing %s\n", gameLocal.GetTime(), delta.ToString() ); + + hhAnimatedEntity::Think(); + +} + + +//============ +// +//============ +int hhAnimDriven::PlayAnim( int channel, const char *animName ) { + int animIndex; + int length; + + + animIndex = GetAnimator()->GetAnim( animName ); + if ( !animIndex ) { + return( 0 ); + } + + //gameLocal.Printf( "Playing Anim %s\n", animName ); + + GetAnimator()->PlayAnim( channel, animIndex, gameLocal.GetTime(), 0 ); + + length = GetAnimator()->CurrentAnim( channel )->GetEndTime() - gameLocal.GetTime(); + + return( length ); + +} + + +//============ +// hhAnimDrive::Event_PlayCycle() +//============ +void hhAnimDriven::Event_PlayCycle( int channel, const char *animName ) { + + PlayCycle( channel, animName ); + +} + + +//============ +// +//============ +bool hhAnimDriven::PlayCycle( int channel, const char *animName ) { + int animIndex; + + animIndex = GetAnimator()->GetAnim( animName ); + if ( !animIndex ) { + return( false ); + } + + //gameLocal.Printf( "Cycling Anim %s\n", animName ); + + GetAnimator()->CycleAnim( channel, animIndex, gameLocal.GetTime(), 0 ); + + return( true ); +} + + +/* +================ +hhAnimDriven::ClearAllAnims +================ +*/ +void hhAnimDriven::ClearAllAnims() { + + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + +} + + +//=========== +// +//============ +bool hhAnimDriven::Collide( const trace_t &collision, const idVec3 &velocity ) { + // If we hit something, and have a passenger, pass along the message + if( passenger.IsValid() ) { + if( !passenger->Collide(collision, velocity) ) { + return( false ); + } + + passenger->Unbind(); + } //. We have a valid passenger + + + // If we made it this far, assume the collision is for real, and + // remove ourselves from interacting with the world + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + GetPhysics()->UnlinkClip(); + ClearAllAnims(); + PostEventMS( &EV_Remove, 0 ); + + return( true ); +} + +bool hhAnimDriven::AllowCollision( const trace_t& collision ) { + if ( !spawnArgs.GetBool( "projectile_collision", "1" ) ) { + idEntity* ent = gameLocal.entities[ collision.c.entityNum ]; + if ( !ent || ent->IsType( idProjectile::Type ) ) { + return false; + } + } + + return true; +} + +void hhAnimDriven::UpdateAnimation( void ) { + PROFILE_SCOPE("Animation", PROFMASK_NORMAL); // HUMANHEAD pdm + + // 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; + } + + // call any frame commands that have happened in the past frame + if ( !fl.hidden ) { + 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; + } + + // update the renderEntity + UpdateVisuals(); + + // the animation is updated + animator.ClearForceUpdate(); +} diff --git a/neo/prey/Prey/game_animDriven.h b/neo/prey/Prey/game_animDriven.h new file mode 100644 index 00000000..d938ea47 --- /dev/null +++ b/neo/prey/Prey/game_animDriven.h @@ -0,0 +1,40 @@ +#ifndef __PREY_GAME_ANIMDRIVEN_H__ +#define __PREY_GAME_ANIMDRIVEN_H__ + +// Class name by Jimmy! :) +class hhAnimDriven : public hhAnimatedEntity { + public: + CLASS_PROTOTYPE( hhAnimDriven ); + + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + void SetPassenger( idEntity *newPassenger, bool orientFromPassenger = true ); + + + // Events + void Event_PlayCycle( int channel, const char *animName ); + + + bool Collide( const trace_t &collision, const idVec3 &velocity ); + int PlayAnim( int channel, const char *animName ); + bool PlayCycle( int channel, const char *animName ); + void ClearAllAnims(); + void UpdateAnimation(); + bool AllowCollision( const trace_t& collision ); + + protected: + hhPhysics_Delta physicsAnim; + + idEntityPtr passenger; + + int spawnTime; + bool hadPassenger; + idVec3 deltaScale; //scale anim delta movement by this +}; + +#endif /* __PREY_GAME_ANIMDRIVEN_H__ */ diff --git a/neo/prey/Prey/game_animatedentity.cpp b/neo/prey/Prey/game_animatedentity.cpp new file mode 100644 index 00000000..2ceb6651 --- /dev/null +++ b/neo/prey/Prey/game_animatedentity.cpp @@ -0,0 +1,424 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_CheckCycleRotate( "" ); +const idEventDef EV_CheckThaw( "" ); +const idEventDef EV_SpawnFxAlongBone( "spawnFXAlongBone", "d" ); +const idEventDef EV_Silent( "", NULL ); + +CLASS_DECLARATION( idAnimatedEntity, hhAnimatedEntity ) + EVENT( EV_CheckCycleRotate, hhAnimatedEntity::Event_CheckAnimatorCycleRotate ) + EVENT( EV_CheckThaw, hhAnimatedEntity::Event_CheckAnimatorThaw ) + EVENT( EV_SpawnFxAlongBone, hhAnimatedEntity::Event_SpawnFXAlongBone ) + EVENT( EV_Thread_SetSilenceCallback,hhAnimatedEntity::Event_SetSilenceCallback ) + EVENT( EV_Silent, hhAnimatedEntity::Event_Silent ) +END_CLASS + +/* +============== +hhAnimatedEntity::Spawn +============== +*/ +void hhAnimatedEntity::Spawn( void ) { + hasFlapInfo = false; + + // nla - Added to allow for 1 frame anims to play/proper bone initialization + if ( GetAnimator() ) { + int anim = GetAnimator()->GetAnim("init"); + if ( anim ) { + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0); + GetAnimator()->ForceUpdate(); + UpdateModel(); + } + } +} + +void hhAnimatedEntity::Save(idSaveGame *savefile) const { + savefile->WriteInt( waitingThread ); + savefile->WriteInt( silentTimeOffset ); + savefile->WriteInt( nextSilentTime ); + savefile->WriteFloat( lastAmplitude ); +} + +void hhAnimatedEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( waitingThread ); + savefile->ReadInt( silentTimeOffset ); + savefile->ReadInt( nextSilentTime ); + savefile->ReadFloat( lastAmplitude ); + hasFlapInfo = false; +} + +/* +============== +hhAnimatedEntity::hhAnimatedEntity +============== +*/ +hhAnimatedEntity::hhAnimatedEntity() { + // WaitForSilence support + waitingThread = 0; + silentTimeOffset = 0; + nextSilentTime = 0; + lastAmplitude = 0.0f; + hasFlapInfo = false; +} + +/* +============== +hhAnimatedEntity::~hhAnimatedEntity +============== +*/ +hhAnimatedEntity::~hhAnimatedEntity() { + Event_Silent(); +} + +/* +============== +hhAnimatedEntity::FillDebugVars +============== +*/ +void hhAnimatedEntity::FillDebugVars(idDict *args, int page) { + switch(page) { + case 1: + args->SetInt("anims", GetAnimator()->NumAnims()); + break; + } + idAnimatedEntity::FillDebugVars(args, page); +} + +/* +============== +hhAnimatedEntity::Think +============== +*/ +void hhAnimatedEntity::Think() { + idAnimatedEntity::Think(); + + //HUMANHEAD: aob - moved from idActor + UpdateWounds(); + //HUMANHEAD END +} + +/* +===================== +hhAnimatedEntity::GetAnimator +===================== +*/ +hhAnimator *hhAnimatedEntity::GetAnimator( void ) { + return &animator; +} + +/* +===================== +hhAnimatedEntity::GetAnimator +===================== +*/ +const hhAnimator *hhAnimatedEntity::GetAnimator( void ) const { + return &animator; +} + +/* +===================== +hhAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + return idAnimatedEntity::GetJointWorldTransform( jointHandle, currentTime, offset, axis ); +} + +/* +===================== +hhAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool hhAnimatedEntity::GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ) { + return GetJointWorldTransform( GetAnimator()->GetJointHandle(jointName), gameLocal.GetTime(), offset, axis ); +} + +/* +===================== +hhAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool hhAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ) { + return GetJointWorldTransform( jointHandle, gameLocal.GetTime(), offset, axis ); +} + +/* +============== +hhAnimatedEntity::UpdateWounds +============== +*/ +void hhAnimatedEntity::UpdateWounds( void ) { +} + +/* +================== +idEntity::SpawnFXAlongBonePrefixLocal +================== +*/ +void hhAnimatedEntity::SpawnFxAlongBonePrefixLocal( const idDict* dict, const char* fxKeyPrefix, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef ) { + idVec3 bonePos; + idMat3 boneAxis; + + for( const idKeyValue* kv = dict->MatchPrefix(bonePrefix); kv; kv = dict->MatchPrefix(bonePrefix, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + GetJointWorldTransform( kv->GetValue().c_str(), bonePos, boneAxis ); + SpawnFXPrefixLocal( dict, fxKeyPrefix, bonePos, boneAxis, fxInfo, eventDef ); + } +} + +/* +================= +hhAnimatedEntity::SpawnFXAlongBone +================= +*/ +void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefix( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) { + return; + } + + idVec3 bonePos; + idMat3 boneAxis; + hhFxInfo localFxInfo( fxInfo ); + + localFxInfo.SetEntity( this ); + localFxInfo.RemoveWhenDone( true ); + + const idKeyValue* keyValue = args->MatchPrefix( bonePrefix ); + while( keyValue && keyValue->GetValue().Length() ) { + + if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) { + //AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized + localFxInfo.SetNormal( boneAxis[0].ToNormal() ); + localFxInfo.SetBindBone( keyValue->GetValue().c_str() ); + + BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef, broadcast ); + } + + keyValue = args->MatchPrefix( bonePrefix, keyValue ); + } +} + +/* +================= +hhAnimatedEntity::SpawnFXAlongBone +mdl +================= +*/ +void hhAnimatedEntity::BroadcastFxInfoAlongBonePrefixUnique( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + if( !fxKey || !fxKey[0] || !bonePrefix || !bonePrefix[0] ) { + return; + } + + idVec3 bonePos; + idMat3 boneAxis; + hhFxInfo localFxInfo( fxInfo ); + + localFxInfo.SetEntity( this ); + localFxInfo.RemoveWhenDone( true ); + + const idKeyValue* keyValue = args->MatchPrefix( bonePrefix ); + idStr postfix; + int prefixLen = strlen( bonePrefix ); + while( keyValue && keyValue->GetValue().Length() ) { + postfix = keyValue->GetKey().Right( keyValue->GetKey().Length() - prefixLen ); + + if( GetJointWorldTransform( keyValue->GetValue().c_str(), bonePos, boneAxis ) ) { + //AOB: HACK - crashed in idBitMsg::DirToBits because we didn't appear to be normalized + localFxInfo.SetNormal( boneAxis[0].ToNormal() ); + localFxInfo.SetBindBone( keyValue->GetValue().c_str() ); + + // Changed this because it was causing every fx listed to be bound to every bone listed. -mdl + BroadcastFxInfo( spawnArgs.GetString( fxKey + postfix ), bonePos, boneAxis, &localFxInfo, eventDef, broadcast ); + //BroadcastFxInfoPrefixed( fxKey, bonePos, boneAxis, &localFxInfo, eventDef ); + } + + keyValue = args->MatchPrefix( bonePrefix, keyValue ); + } +} + +/* +================ +hhAnimatedEntity::SpawnFxAlongBone +================ +*/ +void hhAnimatedEntity::BroadcastFxInfoAlongBone( const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + //mdc: pass through to our newer version of the function (this is mainly done for backwards compatibility) + BroadcastFxInfoAlongBone( false, fxName, boneName, fxInfo, eventDef, broadcast ); +} + +void hhAnimatedEntity::BroadcastFxInfoAlongBone( bool bNoRemoveWhenUnbound, const char* fxName, const char* boneName, const hhFxInfo* const fxInfo, const idEventDef* eventDef, bool broadcast ) { + if( !fxName || !fxName[0] || !boneName || !boneName[0] ) { + return; + } + + idVec3 bonePos; + idMat3 boneAxis; + hhFxInfo localFxInfo( fxInfo ); + + GetJointWorldTransform( boneName, bonePos, boneAxis ); + localFxInfo.SetNormal( boneAxis[0] ); + localFxInfo.SetEntity( this ); + localFxInfo.NoRemoveWhenUnbound( bNoRemoveWhenUnbound ); //mdc: added + localFxInfo.SetBindBone( boneName ); + + BroadcastFxInfo( fxName, bonePos, boneAxis, &localFxInfo, eventDef, broadcast ); +} + +/* +================ +hhAnimatedEntity::Event_SpawnFXAlongBone +================ +*/ +void hhAnimatedEntity::Event_SpawnFXAlongBone( idList* fxParms ) { + if ( !fxParms ) { + return; + } + + bool bNoRemoveWhenUnbound = false; //default to false (we normally want to delete fx when the bound-entity is deleted) + HH_ASSERT( fxParms->Num() >= 2 && fxParms->Num() <= 3 ); //allow 2-3 parameters + if( fxParms->Num() == 3 ) { + //make sure the 3rd paramater is our special noRemoveWhenUnbound flag + if( !idStr::Icmp((*fxParms)[2].c_str(), "noremovewhenunbound") ) { + bNoRemoveWhenUnbound = true; + } + else { + gameLocal.Warning( "unrecognized 3rd paramater on frame command for SpawnFXAlongBone on entity '%s'", this->GetName() ); + } + } + BroadcastFxInfoAlongBone(bNoRemoveWhenUnbound, spawnArgs.GetString((*fxParms)[0].c_str()), (*fxParms)[1].c_str() ); +} + + +// HUMANHEAD pdm: Jaw flapping support +// This provides generalized support for any animated entity to translate voice channel sound to bone movements +void hhAnimatedEntity::JawFlap(hhAnimator *theAnimator) { + PROFILE_SCOPE("Jaw Flap", PROFMASK_NORMAL); + float amplitude = 0.0f; + + if (fl.noJawFlap || !g_jawflap.GetBool()) { + return; + } + + // Get amplitude from head or body + idEntity *headEntity = NULL; + if (IsType(idActor::Type)) { + headEntity = static_cast(this)->GetHead(); + if (headEntity && headEntity->GetSoundEmitter()) { + amplitude = headEntity->GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE ); + } + } + + if( amplitude == 0.0f && GetSoundEmitter() ) { + amplitude = GetSoundEmitter()->CurrentVoiceAmplitude( SND_CHANNEL_VOICE ); + } + + if (amplitude != 0.0f || lastAmplitude != 0.0f) { + //HUMANHEAD rww - only get the joint handles and info for jaw flapping once + //we have to do this here, since the head is not valid at hhAnimatedEntity::Spawn + if (!hasFlapInfo) { + jawFlapList.Clear(); + const char *prefix = "jawbone_"; + const idKeyValue *kv = spawnArgs.MatchPrefix(prefix); + while( kv && kv->GetValue().Length() ) { + jointHandle_t bone = theAnimator->GetJointHandle( kv->GetValue() ); + if (bone != INVALID_JOINT) { + jawFlapInfo_t flapInfo; + + idStr xformName = kv->GetKey(); + xformName.Strip(prefix); + + flapInfo.bone = bone; + flapInfo.rMagnitude = spawnArgs.GetVector( va("jawflapR_%s", xformName.c_str()) ); + flapInfo.tMagnitude = spawnArgs.GetVector( va("jawflapT_%s", xformName.c_str()) ); + flapInfo.rMinThreshold = spawnArgs.GetFloat( va("jawflapRMin_%s", xformName.c_str()) ); + flapInfo.tMinThreshold = spawnArgs.GetFloat( va("jawflapTMin_%s", xformName.c_str()) ); + jawFlapList.Append(flapInfo); + } + kv = spawnArgs.MatchPrefix(prefix, kv); + } + hasFlapInfo = true; + } + + lastAmplitude = amplitude; + //HUMANHEAD rww - changed to use a list and not do constant runtime joint lookups + for (int i = 0; i < jawFlapList.Num(); i++) { + jawFlapInfo_t &flapInfo = jawFlapList[i]; + + // Handle Rotation + idAngles angles = ang_zero; + if (amplitude > flapInfo.rMinThreshold) { + float factor = amplitude - flapInfo.rMinThreshold; + angles = idAngles(factor*flapInfo.rMagnitude[0], factor*flapInfo.rMagnitude[1], factor*flapInfo.rMagnitude[2]); + } + theAnimator->SetJointAxis( flapInfo.bone, JOINTMOD_WORLD, angles.ToMat3() ); + + // Handle translation + idVec3 translate = vec3_origin; + if (amplitude > flapInfo.tMinThreshold) { + float factor = amplitude - flapInfo.tMinThreshold; + translate = idVec3(factor*flapInfo.tMagnitude[0], factor*flapInfo.tMagnitude[1], factor*flapInfo.tMagnitude[2]); + } + theAnimator->SetJointPos( flapInfo.bone, JOINTMOD_WORLD, translate ); + } + } +} + +// WaitForSilence support: Tracks the next time the voice channel will be silent, and if any threads are doing a waitForSilence() block, calls them back. +// If a waitForSilence() call is issued while no sound is playing on the voice channel, it will wait until there is something and then wait for silence. +bool hhAnimatedEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + int localLength = 0; + bool retVal = idAnimatedEntity::StartSoundShader(shader, channel, soundShaderFlags, broadcast, &localLength); + + // need to track 'nextSilentTime' locally also, so if a tiny sound is played after a longer sound, it doesn't think silence will follow short sound + if (channel == SND_CHANNEL_VOICE && (localLength > 0) && (gameLocal.time + localLength > nextSilentTime)) { + nextSilentTime = gameLocal.time + localLength; + + // If there's already a thread waiting for silence and this sound pushes silence out further, repost an event for it + if (waitingThread) { + CancelEvents(&EV_Silent); + int time = localLength+silentTimeOffset; + time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms + PostEventMS(&EV_Silent, time); + } + } + if (length) { + *length = localLength; + } + return retVal; +} + +void hhAnimatedEntity::Event_Silent() { + if (waitingThread) { + idThread::ObjectMoveDone( waitingThread, this ); + waitingThread = 0; + } +} + +void hhAnimatedEntity::Event_SetSilenceCallback(float plusOrMinusSeconds) { + + if (!waitingThread) { + waitingThread = idThread::CurrentThreadNum(); + silentTimeOffset = SEC2MS(plusOrMinusSeconds); + + if (nextSilentTime > gameLocal.time) { + // If sound already playing + int time = (nextSilentTime - gameLocal.time) + silentTimeOffset; + time = idMath::ClampInt(1, time, time); // Keep positive and wait at least 1ms + CancelEvents(&EV_Silent); + PostEventMS(&EV_Silent, time); + } + else { + // No sound playing, wait for sound to play first + } + idThread::ReturnInt( true ); + } + else { + idThread::ReturnInt( false ); + } +} diff --git a/neo/prey/Prey/game_animatedentity.h b/neo/prey/Prey/game_animatedentity.h new file mode 100644 index 00000000..44a1c059 --- /dev/null +++ b/neo/prey/Prey/game_animatedentity.h @@ -0,0 +1,74 @@ +#ifndef __GAME_ANIMATEDENTITY_H +#define __GAME_ANIMATEDENTITY_H + +extern const idEventDef EV_CheckCycleRotate; +extern const idEventDef EV_CheckThaw; +extern const idEventDef EV_SpawnFxAlongBone; + +//HUMANHEAD rww +typedef struct jawFlapInfo_s { + jointHandle_t bone; + idVec3 rMagnitude; + idVec3 tMagnitude; + float rMinThreshold; + float tMinThreshold; +} jawFlapInfo_t; +//HUMANHEAD END + +class hhAnimatedEntity : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( hhAnimatedEntity ); + + void Spawn( void ); + hhAnimatedEntity(); + virtual ~hhAnimatedEntity(); + + void Think(); + + virtual hhAnimator * GetAnimator( void ); + virtual const hhAnimator * GetAnimator( void ) const; + virtual void FillDebugVars( idDict *args, int page ); + + virtual idClipModel* GetCombatModel() const { return combatModel; } + + virtual bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + virtual bool GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ); + virtual bool GetJointWorldTransform( jointHandle_t jointHandle, idVec3 &offset, idMat3 &axis ); + + void JawFlap(hhAnimator *theAnimator); + + void SpawnFxAlongBonePrefixLocal( const idDict* dict, const char* fxKeyPrefix, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL ); + virtual void BroadcastFxInfoAlongBonePrefix( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + virtual void BroadcastFxInfoAlongBonePrefixUnique( const idDict* args, const char* fxKey, const char* bonePrefix, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + virtual void BroadcastFxInfoAlongBone( const char* fxName, const char* boneName, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + virtual void BroadcastFxInfoAlongBone( bool bNoRemoveWhenUnbound, const char* fxName, const char* boneName, const hhFxInfo* const fxInfo = NULL, const idEventDef* eventDef = NULL, bool broadcast = true ); //HUMANHEAD rww - added broadcast + + // WaitForSilence support + virtual bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags = 0, bool broadcast = false, int *length = NULL ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + // WaitForSilence support + int waitingThread; // Thread that's blocking until we tell it we're silent + int silentTimeOffset; // +/- this amount + int nextSilentTime; // Time at which all sounds will be ended + void Event_Silent(); + void Event_SetSilenceCallback(float plusOrMinusSeconds); + + float lastAmplitude; + + bool hasFlapInfo; //HUMANHEAD rww + idList jawFlapList; //HUMANHEAD rww + + //HUMANHEAD: aob - moved from idActor + virtual void UpdateWounds( void ); + virtual hhWoundManagerRenderEntity* CreateWoundManager() const { if (!animator.IsAnimatedModel()) { return idEntity::CreateWoundManager(); } return new hhWoundManagerAnimatedEntity(this); } + +protected: + void Event_CheckAnimatorCycleRotate( void ) { GetAnimator()->CheckCycleRotate(); } + void Event_CheckAnimatorThaw( void ) { GetAnimator()->CheckThaw(); } + void Event_SpawnFXAlongBone( idList* fxParms ); +}; + +#endif diff --git a/neo/prey/Prey/game_animatedgui.cpp b/neo/prey/Prey/game_animatedgui.cpp new file mode 100644 index 00000000..b3c054f4 --- /dev/null +++ b/neo/prey/Prey/game_animatedgui.cpp @@ -0,0 +1,208 @@ +//************************************************************************** +//** +//** hhAnimatedGui +//** +//************************************************************************** + +//TODO: Could impliment a queue for open/close events, so we can wait until +// fully open before closing +// -or- +// could make animations blend out of current and into the new +// requires dynamic control of animation weights + + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const float AG_SMALL_SCALE = 0.01f; +const float AG_LARGE_SCALE = 1.0f; + +CLASS_DECLARATION( hhAnimatedEntity, hhAnimatedGui ) + EVENT(EV_PlayIdle, hhAnimatedGui::Event_PlayIdle) + EVENT(EV_Activate, hhAnimatedGui::Event_Trigger) +END_CLASS + + +//========================================================================== +// +// hhAnimatedGui::Spawn +// +//========================================================================== + +void hhAnimatedGui::Spawn(void) { + idDict args; + + GetPhysics()->SetContents( CONTENTS_BODY ); + + idleOpenAnim = GetAnimator()->GetAnim("idleopen"); + idleCloseAnim = GetAnimator()->GetAnim("idleclose"); + openAnim = GetAnimator()->GetAnim("open"); + closeAnim = GetAnimator()->GetAnim("close"); + + bOpen = false; + guiScale.Init(gameLocal.time, 0, AG_SMALL_SCALE, AG_SMALL_SCALE); + + // Spawn and bind the console on + const char *consoleName = spawnArgs.GetString("def_gui"); + if (consoleName && *consoleName) { + args.Clear(); + args.Set("gui", spawnArgs.GetString("gui_topass")); + args.Set("origin", GetOrigin().ToString()); // need the joint position + args.Set("rotation", GetAxis().ToString()); + attachedConsole = gameLocal.SpawnObject(consoleName, &args); + assert(attachedConsole); + attachedConsole->SetOrigin(GetOrigin() + GetAxis()[0]*10); + attachedConsole->Bind(this, true); + attachedConsole->Hide(); + } + + // Spawn the trigger + const char *triggerName = spawnArgs.GetString("def_trigger"); + if (triggerName && *triggerName) { + args.Clear(); + args.Set( "target", name.c_str() ); + args.Set( "mins", spawnArgs.GetString("triggerMins") ); + args.Set( "maxs", spawnArgs.GetString("triggerMaxs") ); + args.Set( "bind", name.c_str() ); + args.SetVector( "origin", GetOrigin() ); + args.SetMatrix( "rotation", GetAxis() ); + idEntity *trigger = gameLocal.SpawnObject( triggerName, &args ); + } + + if (idleOpenAnim && idleCloseAnim) { + PostEventMS(&EV_PlayIdle, 0); + } +} + +hhAnimatedGui::hhAnimatedGui() { + attachedConsole = NULL; +} + +hhAnimatedGui::~hhAnimatedGui() { +} + +void hhAnimatedGui::Save(idSaveGame *savefile) const { + savefile->WriteBool( bOpen ); + savefile->WriteInt( idleOpenAnim ); + savefile->WriteInt( idleCloseAnim ); + savefile->WriteInt( openAnim ); + savefile->WriteInt( closeAnim ); + + savefile->WriteFloat( guiScale.GetStartTime() ); // idInterpolate + savefile->WriteFloat( guiScale.GetDuration() ); + savefile->WriteFloat( guiScale.GetStartValue() ); + savefile->WriteFloat( guiScale.GetEndValue() ); + + savefile->WriteObject( attachedConsole ); +} + +void hhAnimatedGui::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadBool( bOpen ); + savefile->ReadInt( idleOpenAnim ); + savefile->ReadInt( idleCloseAnim ); + savefile->ReadInt( openAnim ); + savefile->ReadInt( closeAnim ); + + savefile->ReadFloat( set ); // idInterpolate + guiScale.SetStartTime( set ); + savefile->ReadFloat( set ); + guiScale.SetDuration( set ); + savefile->ReadFloat( set ); + guiScale.SetStartValue(set); + savefile->ReadFloat( set ); + guiScale.SetEndValue( set ); + + savefile->ReadObject( reinterpret_cast( attachedConsole ) ); +} + +//========================================================================== +// +// hhAnimatedGui::Think +// +//========================================================================== +void hhAnimatedGui::Think( void ) { + hhAnimatedEntity::Think(); + + if (thinkFlags & TH_THINK) { + float curScale = guiScale.GetCurrentValue(gameLocal.time); + attachedConsole->SetDeformation(DEFORMTYPE_SCALE, curScale); + if (guiScale.IsDone(gameLocal.time)) { + if (curScale == AG_SMALL_SCALE) { // Just finished scaling down + attachedConsole->Hide(); + } + BecomeInactive(TH_THINK); + } + } +} + +//========================================================================== +// +// hhAnimatedGui::Event_PlayIdle +// +//========================================================================== +void hhAnimatedGui::Event_PlayIdle() { + + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + + if (bOpen) { + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleOpenAnim, gameLocal.time, 0); + + FadeInGui(); + } + else { + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleCloseAnim, gameLocal.time, 0); + } +} + + +//========================================================================== +// +// hhAnimatedGui::Event_Trigger +// +//========================================================================== + +void hhAnimatedGui::Event_Trigger( idEntity *activator ) { + + if (!openAnim || !closeAnim) { + return; + } + + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + + int ms; + if(bOpen) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, closeAnim, gameLocal.time, 0); + ms = GetAnimator()->GetAnim( closeAnim )->Length(); + bOpen = false; + + FadeOutGui(); + } + else { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, openAnim, gameLocal.time, 0); + ms = GetAnimator()->GetAnim( openAnim )->Length(); + bOpen = true; + } + + PostEventMS(&EV_PlayIdle, ms); +} + +void hhAnimatedGui::FadeInGui() { + float curScale = guiScale.GetCurrentValue(gameLocal.time); + guiScale.Init(gameLocal.time, 500, curScale, AG_LARGE_SCALE); + BecomeActive(TH_THINK); + attachedConsole->SetDeformation(DEFORMTYPE_SCALE, curScale); + attachedConsole->Show(); +} + +void hhAnimatedGui::FadeOutGui() { + float curScale = guiScale.GetCurrentValue(gameLocal.time); + guiScale.Init(gameLocal.time, 100, curScale, AG_SMALL_SCALE); + BecomeActive(TH_THINK); + attachedConsole->Show(); +} diff --git a/neo/prey/Prey/game_animatedgui.h b/neo/prey/Prey/game_animatedgui.h new file mode 100644 index 00000000..f72f45f6 --- /dev/null +++ b/neo/prey/Prey/game_animatedgui.h @@ -0,0 +1,34 @@ + +#ifndef __GAME_ANIMATEDGUI_H__ +#define __GAME_ANIMATEDGUI_H__ + + +class hhAnimatedGui : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE(hhAnimatedGui); + + void Spawn(void); + hhAnimatedGui(); + virtual ~hhAnimatedGui(); + virtual void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void FadeInGui(); + void FadeOutGui(); + + void Event_Trigger( idEntity *activator ); + void Event_PlayIdle(); + +protected: + bool bOpen; + int idleOpenAnim; + int idleCloseAnim; + int openAnim; + int closeAnim; + idInterpolate guiScale; + idEntity * attachedConsole; +}; + +#endif /* __GAME_ANIMATEDGUI_H__ */ diff --git a/neo/prey/Prey/game_animator.cpp b/neo/prey/Prey/game_animator.cpp new file mode 100644 index 00000000..2f8adfc4 --- /dev/null +++ b/neo/prey/Prey/game_animator.cpp @@ -0,0 +1,251 @@ +//************************************************************************** +//** +//** hhAnimated +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_PositionDefaultPose(""); +const idEventDef EV_StartDefaultAnim(""); +const idEventDef EV_SetAnim( "setAnim", "s" ); // nla +const idEventDef EV_IsAnimDone( "isAnimDone", "d", 'd' ); + +CLASS_DECLARATION( idAnimated, hhAnimated ) + EVENT( EV_Activate, hhAnimated::Event_Activate ) + EVENT( EV_SetAnim, hhAnimated::Event_SetAnim ) + EVENT( EV_IsAnimDone, hhAnimated::Event_IsAnimDone ) + EVENT( EV_AnimDone, hhAnimated::Event_AnimDone ) + EVENT( EV_Animated_Start, hhAnimated::Event_Start ) + EVENT( EV_PositionDefaultPose, hhAnimated::Event_PositionDefaultPose ) + EVENT( EV_StartDefaultAnim, hhAnimated::Event_StartDefaultAnim ) + EVENT( EV_Footstep, hhAnimated::Event_Footstep ) + EVENT( EV_FootstepLeft, hhAnimated::Event_Footstep ) + EVENT( EV_FootstepRight, hhAnimated::Event_Footstep ) +END_CLASS + +/* +=============== +hhAnimated::Spawn +================ +*/ +void hhAnimated::Spawn() { + isAnimDone = true; + + fl.takedamage = health > 0; + + const char* startAnimName = spawnArgs.GetString( "start_anim" ); + if( !startAnimName || !startAnimName[0] ) { + PostEventMS( &EV_PositionDefaultPose, 0 ); + } +} + +void hhAnimated::Save(idSaveGame *savefile) const { + savefile->WriteBool( isAnimDone ); +} + +void hhAnimated::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( isAnimDone ); +} + +/* +=============== +hhAnimated::Damage +================ +*/ +void hhAnimated::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( CheckRagdollDamage( inflictor, attacker, dir, damageDefName, location ) ) { + return; + } + + idAnimated::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +=============== +hhAnimated::Killed +================ +*/ +void hhAnimated::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + idAnimBlend* pAnim = NULL;//GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, "death", gameLocal.time, 0, false ); + + if( !af.IsActive() ) { + PostEventMS( &EV_StartRagdoll, (pAnim) ? pAnim->Length() : 0 ); + } +} + +/* +=============== +hhAnimated::StartRagdoll +================ +*/ +bool hhAnimated::StartRagdoll( void ) { + bool parentAns = false; + float slomoStart, slomoEnd; + + // NOTE: These first two calls are ripped from idAnimated::StartRagdoll() + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // HUMANHEAD nla - Added to fix the issue with inproper offsets for fixed constraints. Taken from idAFEntity::LoadAF (Caused ragdolls to be 'fixed' to the center of the world/0,0,0) + af.GetPhysics()->Rotate( GetPhysics()->GetAxis().ToRotation() ); + af.GetPhysics()->Translate( GetPhysics()->GetOrigin() ); + // HUMANHEAD END + + parentAns = idAnimated::StartRagdoll(); + + // HUMANHEAD nla - Allow the ragdolls to slow into the ragdoll + 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 ); + + // Allow ragdolls to be active when first started + // This logic ripped from idAFEntity::LoadAF + af.GetPhysics()->PutToRest(); + af.GetPhysics()->Activate(); + + af.UpdateAnimation(); + GetAnimator()->CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + // HUMANHEAD END + + return( parentAns ); +} + +/* +================ +hhAnimated::UpdateAnimationControllers +================ +*/ +bool hhAnimated::UpdateAnimationControllers( void ) { + bool retValue = idAnimated::UpdateAnimationControllers(); + + JawFlap(GetAnimator()); + + return retValue; +} + +/* +================ +hhAnimated::Event_SetAnim +================ +*/ +void hhAnimated::Event_SetAnim( const char *animname ) { + assert( animname ); + + anim = GetAnimator()->GetAnim( animname ); + HH_ASSERT( anim ); + + ProcessEvent( &EV_Activate, this ); +} + +/* +================ +hhAnimated::Event_Start +================ +*/ +void hhAnimated::Event_Start( void ) { + idAnimated::Event_Start(); + + isAnimDone = false; +} + +/* +=============== +hhAnimated::Event_AnimDone +================ +*/ +void hhAnimated::Event_AnimDone( int animIndex ) { + idAnimated::Event_AnimDone( animIndex ); + + if( !spawnArgs.GetBool("resetDefaultAnim") ) { + isAnimDone = true; + } else { + PostEventMS( &EV_StartDefaultAnim, 0 ); + } +} + +/* +================ +hhAnimated::Event_IsAnimDone +================ +*/ +void hhAnimated::Event_IsAnimDone( int timeMS ) { + idThread::ReturnInt( (int) isAnimDone ); +} + +/* +=============== +hhAnimated::Event_PositionDefaultPose +================ +*/ +void hhAnimated::Event_PositionDefaultPose() { + const char* animName = spawnArgs.GetString( "defaultPose" ); + + if( !animName || !animName[0] ) { + return; + } + + int pAnim = GetAnimator()->GetAnim( animName ); + GetAnimator()->SetFrame( ANIMCHANNEL_ALL, pAnim, spawnArgs.GetInt("pose_frame", "1"), gameLocal.GetTime(), 0 ); + + GetAnimator()->ForceUpdate(); + UpdateModel(); + UpdateAnimation(); +} + +/* +=============== +hhAnimated::Event_StartDefaultAnim +================ +*/ +void hhAnimated::Event_StartDefaultAnim() { + const char* animName = spawnArgs.GetString( "start_anim" ); + + if( !animName || animName[0] ) { + return; + } + + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + int pAnim = GetAnimator()->GetAnim( animName ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, pAnim, gameLocal.time, FRAME2MS(blendFrames) ); +} + + +/* +=============== +hhAnimated::Event_Activate +================ +*/ +void hhAnimated::Event_Activate( idEntity *_activator ) { + if (spawnArgs.GetBool("ragdollOnTrigger")) { + StartRagdoll(); + return; + } + + idAnimated::Event_Activate(_activator); +} + +/* +=============== +hhAnimated::Event_Footstep +================ +*/ +void hhAnimated::Event_Footstep() { + StartSound( "snd_footstep", SND_CHANNEL_BODY3, 0, false, NULL ); +} diff --git a/neo/prey/Prey/game_animator.h b/neo/prey/Prey/game_animator.h new file mode 100644 index 00000000..392b5a03 --- /dev/null +++ b/neo/prey/Prey/game_animator.h @@ -0,0 +1,32 @@ + +#ifndef __GAME_ANIMATED_H__ +#define __GAME_ANIMATED_H__ + +class hhAnimated : public idAnimated { + CLASS_PROTOTYPE( hhAnimated ); + + public: + void Spawn(); + 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 bool UpdateAnimationControllers( void ); + + bool StartRagdoll( void ); + + protected: + void Event_SetAnim( const char* animname ); + void Event_IsAnimDone( int timeMS ); + virtual void Event_AnimDone( int animIndex ); + void Event_Start( void ); + void Event_PositionDefaultPose(); + void Event_StartDefaultAnim(); + virtual void Event_Activate( idEntity *_activator ); + void Event_Footstep(); + + protected: + bool isAnimDone; +}; + +#endif /* __GAME_ANIMATED_H__ */ diff --git a/neo/prey/Prey/game_arcadegame.cpp b/neo/prey/Prey/game_arcadegame.cpp new file mode 100644 index 00000000..4cfdce99 --- /dev/null +++ b/neo/prey/Prey/game_arcadegame.cpp @@ -0,0 +1,766 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define DEFAULT_HIGH_SCORE 76535 +#define DEFAULT_HIGH_SCORE_NAME "Teacher" +#define FRUIT_DELAY 15000 + +const idEventDef EV_GameStart("", NULL); +const idEventDef EV_PlayerRespawn("", NULL); +const idEventDef EV_MonsterRespawn("", "d"); +const idEventDef EV_ToggleFruit("", NULL); +const idEventDef EV_GameOver("", NULL); +const idEventDef EV_NextMap("", NULL); + +CLASS_DECLARATION(hhConsole, hhArcadeGame) + EVENT( EV_GameStart, hhArcadeGame::Event_GameStart) + EVENT( EV_PlayerRespawn, hhArcadeGame::Event_PlayerRespawn) + EVENT( EV_MonsterRespawn, hhArcadeGame::Event_MonsterRespawn) + EVENT( EV_ToggleFruit, hhArcadeGame::Event_ToggleFruit) + EVENT( EV_GameOver, hhArcadeGame::Event_GameOver) + EVENT( EV_NextMap, hhArcadeGame::Event_NextMap) + EVENT( EV_CallGuiEvent, hhArcadeGame::Event_CallGuiEvent) +END_CLASS + + +hhArcadeGame::hhArcadeGame() { +} + +hhArcadeGame::~hhArcadeGame() { +} + +void hhArcadeGame::Spawn() { + highscore = DEFAULT_HIGH_SCORE; + highscoreName = DEFAULT_HIGH_SCORE_NAME; + + GetMazeForLevel(1); + victoryAmount = spawnArgs.GetInt("victory"); + bPlayerMoving = false; + playerMove.Set(1,0); + bGameOver = true; + ResetMap(); + ResetPlayerAndMonsters(); + ResetGame(); + UpdateView(); +} + +void hhArcadeGame::GetMazeForLevel(int lev) { + numMazes = idMath::ClampInt(1, 999, spawnArgs.GetInt("numMazes")); + const char *maze = spawnArgs.GetString(va("maze%d", (lev-1)%numMazes+1)); + assert(idStr::Length(maze) == (ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT)); + memset(startingGrid, 0, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); + for (int y=0; yWrite(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); + for (i=0; iWriteVec2(playerMove); + savefile->WriteBool(bPlayerMoving); + savefile->WriteBool(bSimulating); + savefile->WriteBool(bGameOver); + savefile->WriteBool(bPoweredUp); + savefile->WriteInt(score); + savefile->WriteInt(lives); + savefile->WriteInt(level); + savefile->WriteInt(nextScoreBoost); + savefile->WriteInt(nextPelletTime); + savefile->WriteInt(victoryAmount); + savefile->WriteInt(numMazes); + savefile->WriteInt(highscore); + savefile->WriteString(highscoreName); +} + +void hhArcadeGame::Restore( idRestoreGame *savefile ) { + int i,j; + savefile->Read(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); + for (i=0; iReadVec2(playerMove); + savefile->ReadBool(bPlayerMoving); + savefile->ReadBool(bSimulating); + savefile->ReadBool(bGameOver); + savefile->ReadBool(bPoweredUp); + savefile->ReadInt(score); + savefile->ReadInt(lives); + savefile->ReadInt(level); + savefile->ReadInt(nextScoreBoost); + savefile->ReadInt(nextPelletTime); + savefile->ReadInt(victoryAmount); + savefile->ReadInt(numMazes); + savefile->ReadInt(highscore); + savefile->ReadString(highscoreName); +} + +void hhArcadeGame::ConsoleActivated() { + BecomeActive(TH_MISC3); +} + +void hhArcadeGame::StartGame() { + if (bGameOver) { + ResetMap(); + ResetPlayerAndMonsters(); + StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); + ResetGame(); + StartSound("snd_coin", SND_CHANNEL_ANY, 0, true, NULL); + PostEventMS(&EV_GameStart, 2000); + bGameOver = false; + UpdateView(); + } +} + +void hhArcadeGame::FindType(int type, int &xOut, int &yOut, int skipX, int skipY) { + xOut = 0; + yOut = 0; + for (int x=0; xSetStateInt("player_x", guiPos.x); + gui->SetStateInt("player_y", guiPos.y); + gui->SetStateBool("player_vis", player.IsVisible()); + gui->SetStateBool("player_moving", bPlayerMoving); + gui->SetStateFloat("player_angle", player.angle); + gui->SetStateBool("player_dying", player.dying); + } + + for (ix=0; ix<4; ix++) { + if (monsters[ix].Changed()) { + guiPos = SmoothMovement(monsters[ix]); + gui->SetStateInt(va("monster%d_x", ix+1), guiPos.x); + gui->SetStateInt(va("monster%d_y", ix+1), guiPos.y); + gui->SetStateBool(va("monster%d_vis", ix+1), monsters[ix].IsVisible()); + gui->SetStateInt(va("monster%d_pu", ix+1), monsters[ix].vulnerableTimeRemaining); + gui->SetStateFloat(va("monster%d_angle", ix+1), monsters[ix].angle); + } + } + + // Traverse grid, telling gui about state of the pellets and powerups + int curPowerup = 1; + int curPellet = 1; + for (int x=0; xSetStateInt(va("pellet%d_x", curPellet), x * ARCADE_CELL_WIDTH); + gui->SetStateInt(va("pellet%d_y", curPellet), y * ARCADE_CELL_HEIGHT); + gui->SetStateBool(va("pellet%d_vis", curPellet), grid[x][y].IsVisible()); + curPellet++; + } + else if (grid[x][y].GetType() == ARCADE_POWERUP) { + gui->SetStateInt(va("powerup%d_x", curPowerup), x * ARCADE_CELL_WIDTH); + gui->SetStateInt(va("powerup%d_y", curPowerup), y * ARCADE_CELL_HEIGHT); + gui->SetStateBool(va("powerup%d_vis", curPowerup), grid[x][y].IsVisible()); + curPowerup++; + } + } + } + } + + if (fruit.Changed()) { + gui->SetStateInt("fruit_x", fruit.x * ARCADE_CELL_WIDTH); + gui->SetStateInt("fruit_y", fruit.y * ARCADE_CELL_HEIGHT); + gui->SetStateBool("fruit_vis", fruit.IsVisible()); + } + + if (score > highscore) { + highscore = score; + highscoreName = cvarSystem->GetCVarString("ui_name"); + } + + gui->SetStateInt("maze", (level-1)%numMazes+1); + gui->SetStateInt("level", level); + gui->SetStateInt("score", score); + gui->SetStateInt("lives", lives); + gui->SetStateInt("highscore", highscore); + gui->SetStateString("highscorename", highscoreName.c_str()); + gui->SetStateBool("gameover", bGameOver); + gui->StateChanged(gameLocal.time, true); + } +} + +bool hhArcadeGame::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("startgame") == 0) { + // Trigger this entity, used for tip system + idEntity *agTrig = gameLocal.FindEntity( "arcadegame_used" ); + if( agTrig ) { + agTrig->PostEventMS( &EV_Activate, 0, this ); + } + + BecomeActive(TH_MISC3); + StartGame(); + } + else if (token.Icmp("reset") == 0) { + BecomeInactive(TH_MISC3); + bSimulating = false; + bGameOver = true; + bPlayerMoving = false; + playerMove.Set(1,0); + ResetMap(); + ResetPlayerAndMonsters(); + ResetGame(); + highscore = 1000; + highscoreName = DEFAULT_HIGH_SCORE_NAME; + + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhArcadeGame::PlayerControls(usercmd_t *cmd) { + if (bGameOver && (cmd->buttons & BUTTON_ATTACK)) { + StartGame(); + } + + // These come from the player every tick + if (cmd->rightmove != 0 || cmd->forwardmove != 0) { + playerMove.x = cmd->rightmove > 0 ? 1 : cmd->rightmove < 0 ? -1 : 0; + playerMove.y = cmd->forwardmove < 0 ? 1 : cmd->forwardmove > 0 ? -1 : 0; + } +} + +idVec2 MoveForDirection(int dir) { + switch(dir) { + case PAC_MOVE_LEFT: return idVec2(-1, 0); + case PAC_MOVE_RIGHT: return idVec2(1, 0); + case PAC_MOVE_UP: return idVec2(0, -1); + case PAC_MOVE_DOWN: return idVec2(0, 1); + } + return idVec2(0,0); +} + +bool hhArcadeGame::MoveIsValid(idVec2 &move, MovingGamePiece &piece) { + int newX = piece.x + move.x; + int newY = piece.y + move.y; + int cell = grid[newX][newY].GetType(); + + if (cell == ARCADE_CLIPPLAYER) { + // Hack: disallow reentry into ghost cage. Assumes the monster start is adjacent to the playerclip + int sourceCell = grid[piece.x][piece.y].GetType(); + if (sourceCell != ARCADE_MONSTERSTART) { + return false; + } + } + return (cell != ARCADE_SOLID && (cell != ARCADE_CLIPPLAYER || !piece.isPlayer ) ); +} + +void hhArcadeGame::GrowPellets() { + if (!bSimulating) { + return; + } + + int growth = 0; + int i, j; + int x, y; + int startX = gameLocal.random.RandomInt(ARCADE_GRID_WIDTH); + int startY = gameLocal.random.RandomInt(ARCADE_GRID_HEIGHT); + + StartSound("snd_grow", SND_CHANNEL_ANY, 0, true, NULL); + + for (i=0; i 4) { + return; + } + } + } + } +} + +void hhArcadeGame::CheckForCollisions(MovingGamePiece &piece) { + int x = piece.x; + int y = piece.y; + int ix; + int type; + int destX, destY; + + switch(grid[x][y].GetType()) { + case ARCADE_TELEPORT: + FindType(ARCADE_TELEPORT, destX, destY, x, y); + piece.Set(destX, destY); + break; + + case ARCADE_PELLET: + if (piece.isPlayer && grid[x][y].IsVisible()) { + grid[x][y].SetVisible(false); + AddScore(1); + StartSound("snd_pellet", SND_CHANNEL_ANY, 0, true, NULL); + } + break; + + case ARCADE_POWERUP: + if (piece.isPlayer && grid[x][y].IsVisible()) { + AddScore(50); + grid[x][y].SetVisible(false); + monsters[0].vulnerableTimeRemaining += 5000 / level; + monsters[1].vulnerableTimeRemaining += 5000 / level; + monsters[2].vulnerableTimeRemaining += 5000 / level; + monsters[3].vulnerableTimeRemaining += 5000 / level; + bPoweredUp = true; + StopSound(SND_CHANNEL_ITEM, true); + StartSound("snd_powerup", SND_CHANNEL_ANY, 0, true, NULL); + StartSound("snd_poweruploop", SND_CHANNEL_ITEM, 0, true, NULL); + } + break; + } + + if (piece.isPlayer) { + if (fruit.IsVisible() && piece.x==fruit.x && piece.y==fruit.y) { + fruit.SetVisible(false); + AddScore(1000*level); + StartSound("snd_fruit", SND_CHANNEL_ANY, 0, true, NULL); + CancelEvents(&EV_ToggleFruit); + PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); + } + } + + // player / monster collisions + for (ix=0; ix<4; ix++) { + if (monsters[ix].IsVisible() && player.x==monsters[ix].x && player.y==monsters[ix].y) { + if (monsters[ix].vulnerableTimeRemaining > 0) { + monsters[ix].SetVisible(false); + StartSound("snd_eatmonster", SND_CHANNEL_ANY, 0, true, NULL); + AddScore(500); + PostEventMS(&EV_MonsterRespawn, 3000, ix); + } + else if (!gameLocal.GetLocalPlayer()->godmode) { + bSimulating = false; + player.SetVisible(false); + player.dying = true; + StartSound("snd_die", SND_CHANNEL_ANY, 0, true, NULL); + if (lives <= 0) { + PostEventMS(&EV_GameOver, 0); + } + else { + lives--; + PostEventMS(&EV_PlayerRespawn, 2000); + } + return; + } + } + } + + // Check for life bonus + if (score >= nextScoreBoost) { + lives++; + nextScoreBoost *= 2; + StartSound("snd_lifeboost", SND_CHANNEL_ANY, 0, true, NULL); + } + + // Check for level completion + if (piece.isPlayer) { + bool levelCompleted = true; + for (x=0; levelCompleted && x 0 && (level+1 >= victoryAmount)) { + ActivateTargets( gameLocal.GetLocalPlayer() ); + StartSound( "snd_victory", SND_CHANNEL_ANY ); + victoryAmount = 0; + } + StartSound("snd_leveldone", SND_CHANNEL_ANY, 0, true, NULL); + PostEventMS(&EV_NextMap, 2000); + } + } +} + +bool hhArcadeGame::DoMove(idVec2 &move, MovingGamePiece &piece) { + + bool bMoved = move.x || move.y; + piece.Move(move); + + if (piece.isPlayer) { + // Acount for player movement direction + bPlayerMoving = bMoved; + } + + CheckForCollisions(piece); + + return bMoved; +} + +void hhArcadeGame::DoPlayerMove() { + bPlayerMoving = false; + if (!bSimulating) { + return; + } + + bool bValidXMove = playerMove.x && MoveIsValid(idVec2(playerMove.x, 0), player); + bool bValidYMove = playerMove.y && MoveIsValid(idVec2(0, playerMove.y), player); + + // Allow move direction to alternate between X and Y + if (player.lastMove.x) { + if (bValidYMove) { + DoMove(idVec2(0, playerMove.y), player); + } + else if (bValidXMove) { + DoMove(idVec2(playerMove.x, 0), player); + } + else if (MoveIsValid(player.lastMove, player)) { + DoMove(player.lastMove, player); + } + } + else { + if (bValidXMove) { + DoMove(idVec2(playerMove.x, 0), player); + } + else if (bValidYMove) { + DoMove(idVec2(0, playerMove.y), player); + } + else if (MoveIsValid(player.lastMove, player)) { + DoMove(player.lastMove, player); + } + } +} + +void hhArcadeGame::DoMonsterAI(MovingGamePiece &monster, int index) { + if (!bSimulating) { + return; + } + + if (monster.vulnerableTimeRemaining > 0) { + monster.vulnerableTimeRemaining -= monster.moveDelay; + monster.vulnerableTimeRemaining = idMath::ClampInt(0, 100000, monster.vulnerableTimeRemaining); + } + + idVec2 move; + idVec2 toPlayer; + idVec2 toPlayerMove; + int playerDirX, playerDirY; + toPlayer.Set(player.x - monster.x, player.y - monster.y); + playerDirX = toPlayer.x < 0 ? PAC_MOVE_LEFT : PAC_MOVE_RIGHT; + playerDirY = toPlayer.y < 0 ? PAC_MOVE_UP : PAC_MOVE_DOWN; + toPlayerMove.Set( + toPlayer.x > 0 ? 1 : toPlayer.x < 0 ? -1 : 0, + toPlayer.y > 0 ? 1 : toPlayer.y < 0 ? -1 : 0 + ); + idVec2 illegalMove = -monster.lastMove; + bool bGoTowardPlayer = gameLocal.random.RandomInt(4) >= index; + int choice; + int startingChoice = index; // Makes each monster unique + int j; + + // Keep moving in direction of last move until there is an intersection + // At intersection, choose between one of the directions, favoring one going towards + // the player. Never go back in the direction you came from unless at a dead end. + + // Go towards player if possible + if (bGoTowardPlayer) { + move = MoveForDirection(playerDirX); + if (MoveIsValid(move, monster) && move != illegalMove) { + DoMove(move, monster); + return; + } + move = MoveForDirection(playerDirY); + if (MoveIsValid(move, monster) && move != illegalMove) { + DoMove(move, monster); + return; + } + } + + // Otherwise move in one of the other valid directions + for (j=0; j<4; j++) { + choice = (startingChoice + j) % 4; + move = MoveForDirection(choice); + if (MoveIsValid(move, monster) && move != illegalMove) { + DoMove(move, monster); + return; + } + } + + // If still haven't found a valid move, must be at a dead end, now allow illegalMove + for (j=0; j<4; j++) { + choice = (startingChoice + j) % 4; + move = MoveForDirection(choice); + if (MoveIsValid(move, monster)) { + DoMove(move, monster); + return; + } + } +} + +void hhArcadeGame::Think() { + hhConsole::Think(); + + if (thinkFlags & TH_MISC3) { + if (bSimulating) { + // Player + if (gameLocal.time > player.nextMoveTime) { + player.nextMoveTime = gameLocal.time + player.moveDelay; + if (playerMove.LengthSqr() > 0.0f) { + DoPlayerMove(); + } + } + + // AI + for (int ix=0; ix<4; ix++) { + if (gameLocal.time > monsters[ix].nextMoveTime) { + monsters[ix].nextMoveTime = gameLocal.time + monsters[ix].moveDelay; + DoMonsterAI(monsters[ix], ix); + } + } + + // See if power up sound should end + if (bPoweredUp) { + bPoweredUp = false; + for (int ix=0; ix<4; ix++) { + if (monsters[ix].vulnerableTimeRemaining > 0) { + bPoweredUp = true; + break; + } + } + if (!bPoweredUp) { + StopSound(SND_CHANNEL_ITEM, true); + } + } + + if (gameLocal.time > nextPelletTime) { + nextPelletTime = gameLocal.time + 3500; + //GrowPellets(); + } + + UpdateView(); + } + } +} + +void hhArcadeGame::AddScore(int amount) { + if (!gameLocal.GetLocalPlayer()->godmode) { + score += amount; + } +} + +void hhArcadeGame::Event_GameStart() { + bSimulating = true; + PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); +} + +void hhArcadeGame::Event_GameOver() { // Player ran out of lives + CancelEvents(&EV_ToggleFruit); + StopSound(SND_CHANNEL_ITEM, true); + bSimulating = false; + bGameOver = true; + CallNamedEvent("gameover"); + UpdateView(); +} + +void hhArcadeGame::Event_NextMap() { // Transition to next map + CancelEvents(&EV_ToggleFruit); + StopSound(SND_CHANNEL_ITEM, true); + GetMazeForLevel(++level); + ResetMap(); + ResetPlayerAndMonsters(); + StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); + PostEventMS(&EV_GameStart, 2000); + UpdateView(); +} + +void hhArcadeGame::Event_PlayerRespawn() { // Player died, respawn + CancelEvents(&EV_ToggleFruit); + ResetPlayerAndMonsters(); + StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); + player.SetVisible(true); + player.dying = false; + PostEventMS(&EV_GameStart, 3000); + UpdateView(); +} + +void hhArcadeGame::Event_MonsterRespawn(int index) { // Monster died, respawn + int x,y; + FindType(ARCADE_MONSTERSTART, x, y); + monsters[index].SetVisible(true); + monsters[index].vulnerableTimeRemaining = 0; + monsters[index].Set(x,y); + monsters[index].nextMoveTime = gameLocal.time + 3000; +} + +void hhArcadeGame::Event_ToggleFruit() { + if (fruit.IsVisible()) { + fruit.SetVisible(false); + PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); + } + else { + fruit.SetVisible(true); + PostEventMS(&EV_ToggleFruit, 5000); + } +} + +void hhArcadeGame::Event_CallGuiEvent(const char *eventName) { + + if (!idStr::Icmp(eventName, "turnoff")) { + // Release any players routing commands to us + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType(hhPlayer::Type) ) { + hhPlayer *player = static_cast(gameLocal.entities[i]); + if (player->guiWantsControls.IsValid() && player->guiWantsControls.GetEntity() == this) { + player->guiWantsControls = NULL; + } + } + } + } + + CallNamedEvent(eventName); +} + +void hhArcadeGame::LockedGuiReleased(hhPlayer *player) { + idEntity *agTrig = gameLocal.FindEntity( "arcadegame_released" ); + if( agTrig ) { + agTrig->PostEventMS( &EV_Activate, 0, this ); + } +} + diff --git a/neo/prey/Prey/game_arcadegame.h b/neo/prey/Prey/game_arcadegame.h new file mode 100644 index 00000000..eae06351 --- /dev/null +++ b/neo/prey/Prey/game_arcadegame.h @@ -0,0 +1,188 @@ + +#ifndef __GAME_ARCADEGAME_H__ +#define __GAME_ARCADEGAME_H__ + +#define ARCADE_GRID_WIDTH 19 +#define ARCADE_GRID_HEIGHT 21 +#define ARCADE_CELL_WIDTH 20 +#define ARCADE_CELL_HEIGHT 20 + +#define ARCADE_EMPTY 0 +#define ARCADE_SOLID 1 +#define ARCADE_PELLET 2 +#define ARCADE_POWERUP 3 +#define ARCADE_CLIPPLAYER 4 +#define ARCADE_TELEPORT 5 +#define ARCADE_FRUIT 6 +#define ARCADE_PLAYERSTART 7 +#define ARCADE_MONSTERSTART 8 + +enum { + PAC_MOVE_LEFT=0, + PAC_MOVE_RIGHT=1, + PAC_MOVE_UP=2, + PAC_MOVE_DOWN=3 +}; + +class GamePiece { +public: + GamePiece() { type = ARCADE_EMPTY; visible = true; changed = true; } + virtual ~GamePiece() { } + void SetType(char t) { type = t; changed = true; } + void SetVisible(bool v) { visible = v; changed = true; } + char GetType() { return type; } + bool IsVisible() { return visible; } + bool Changed() { return changed; } + virtual void Save( idSaveGame *savefile ) const { + savefile->WriteByte(type); + savefile->WriteBool(visible); + savefile->WriteBool(changed); + } + virtual void Restore( idRestoreGame *savefile ) { + savefile->ReadByte(type); + savefile->ReadBool(visible); + savefile->ReadBool(changed); + } + +protected: + byte type; + bool visible; + bool changed; +}; + +class MovingGamePiece : public GamePiece { +public: + MovingGamePiece() { + x = y = 0; isPlayer = false; dying = false; + lastMove.Zero(); + lastMoveTime = 0; + angle = 0.0f; + moveDelay = 250; + nextMoveTime = 0; + vulnerableTimeRemaining = 0; + } + void Set(int nx, int ny, bool nvis=true) { + x=nx; y=ny; visible = nvis; + lastMove.Zero(); + lastMoveTime = gameLocal.time; + changed = true; + } + void Move(idVec2 move) { + x += move.x; + y += move.y; + lastMove = move; + lastMoveTime = gameLocal.time; + angle = + lastMove.x > 0 ? 0.0f : + lastMove.x < 0 ? 180.0f : + lastMove.y < 0 ? 90.0f : + lastMove.y > 0 ? 270.0f : angle; + + changed = true; + } + virtual void Save( idSaveGame *savefile ) const { + GamePiece::Save(savefile); + savefile->WriteInt(x); + savefile->WriteInt(y); + savefile->WriteBool(isPlayer); + savefile->WriteFloat(angle); + savefile->WriteBool(dying); + savefile->WriteVec2(lastMove); + savefile->WriteInt(lastMoveTime); + savefile->WriteInt(moveDelay); + savefile->WriteInt(nextMoveTime); + savefile->WriteInt(vulnerableTimeRemaining); + } + virtual void Restore( idRestoreGame *savefile ) { + GamePiece::Restore(savefile); + savefile->ReadInt(x); + savefile->ReadInt(y); + savefile->ReadBool(isPlayer); + savefile->ReadFloat(angle); + savefile->ReadBool(dying); + savefile->ReadVec2(lastMove); + savefile->ReadInt(lastMoveTime); + savefile->ReadInt(moveDelay); + savefile->ReadInt(nextMoveTime); + savefile->ReadInt(vulnerableTimeRemaining); + } + + int x,y; + bool isPlayer; + bool dying; // used for player only + idVec2 lastMove; + float angle; + int lastMoveTime; + int moveDelay; // Delay in MS between moves + int nextMoveTime; // Next time piece is allowed to move + int vulnerableTimeRemaining; // monsters only, time before powerup wears off +}; + + +class hhArcadeGame : public hhConsole { +public: + CLASS_PROTOTYPE( hhArcadeGame ); + + hhArcadeGame(); + virtual ~hhArcadeGame(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think(); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void PlayerControls(usercmd_t *cmd); + virtual void ConsoleActivated(); + virtual void LockedGuiReleased(hhPlayer *player); + + void StartGame(); + bool MoveIsValid(idVec2 &move, MovingGamePiece &piece); + bool DoMove(idVec2 &move, MovingGamePiece &piece); + void CheckForCollisions(MovingGamePiece &piece); + void ResetMap(); + void ResetPlayerAndMonsters(); + void ResetGame(); + void GrowPellets(); + void DoPlayerMove(); + void DoMonsterAI(MovingGamePiece &monster, int index); + idVec2 SmoothMovement(MovingGamePiece &piece); + void UpdateView(); + void FindType(int type, int &x, int &y, int skipX=-1, int skipY=-1); + void GetMazeForLevel(int lev); + void AddScore(int amount); + +protected: + void Event_GameStart(); + void Event_GameOver(); + void Event_PlayerRespawn(); + void Event_MonsterRespawn(int index); + void Event_ToggleFruit(); + void Event_NextMap(); + void Event_CallGuiEvent(const char *eventName); + +protected: + char startingGrid[ARCADE_GRID_WIDTH][ARCADE_GRID_HEIGHT]; + GamePiece grid[ARCADE_GRID_WIDTH][ARCADE_GRID_HEIGHT]; + MovingGamePiece monsters[4]; + MovingGamePiece player; + MovingGamePiece fruit; + idVec2 playerMove; + idStr highscoreName; + bool bPlayerMoving; + bool bSimulating; + bool bGameOver; + bool bPoweredUp; + int score; + int highscore; + int lives; + int level; + int numMazes; + int nextScoreBoost; + int nextPelletTime; + +private: + int victoryAmount; +}; + + +#endif diff --git a/neo/prey/Prey/game_barrel.cpp b/neo/prey/Prey/game_barrel.cpp new file mode 100644 index 00000000..d3baf1b0 --- /dev/null +++ b/neo/prey/Prey/game_barrel.cpp @@ -0,0 +1,181 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhMoveable, hhBarrel ) +END_CLASS + +/* +================ +hhBarrel::hhBarrel +================ +*/ +hhBarrel::hhBarrel() { + radius = 1.0f; + barrelAxis = 0; + lastOrigin.Zero(); + lastAxis.Identity(); + additionalRotation = 0.0f; + additionalAxis.Identity(); + fl.networkSync = true; +} + +/* +================ +hhBarrel::Save +================ +*/ +void hhBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteInt( barrelAxis ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( lastAxis ); + savefile->WriteFloat( additionalRotation ); + savefile->WriteMat3( additionalAxis ); +} + +/* +================ +hhBarrel::Restore +================ +*/ +void hhBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadInt( barrelAxis ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( lastAxis ); + savefile->ReadFloat( additionalRotation ); + savefile->ReadMat3( additionalAxis ); +} + +/* +================ +hhBarrel::BarrelThink +================ +*/ +void hhBarrel::BarrelThink( void ) { + bool wasAtRest, onGround; + float movedDistance, rotatedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + idMat3 curAxis, axis; + + wasAtRest = IsAtRest(); + + // 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(); +} + +/* +================ +hhBarrel::Think +================ +*/ +void hhBarrel::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() ) { + BecomeInactive( TH_THINK ); + } + } + + BarrelThink(); +} + +/* +================ +hhBarrel::GetPhysicsToVisualTransform +================ +*/ +bool hhBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis; + return true; +} + +/* +================ +hhBarrel::Spawn +================ +*/ +void hhBarrel::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(); +} + +/* +================ +hhBarrel::ClientPredictionThink +================ +*/ +void hhBarrel::ClientPredictionThink( void ) { + Think(); +} + + diff --git a/neo/prey/Prey/game_barrel.h b/neo/prey/Prey/game_barrel.h new file mode 100644 index 00000000..a82c5429 --- /dev/null +++ b/neo/prey/Prey/game_barrel.h @@ -0,0 +1,30 @@ +#ifndef __HH_BARREL_H +#define __HH_BARREL_H + +class hhBarrel : public hhMoveable { + +public: + CLASS_PROTOTYPE( hhBarrel ); + hhBarrel(); + + 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 +}; + + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_bindController.cpp b/neo/prey/Prey/game_bindController.cpp new file mode 100644 index 00000000..66479107 --- /dev/null +++ b/neo/prey/Prey/game_bindController.cpp @@ -0,0 +1,290 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhBindController +// +// These can be bound to anything. They can be used to attach a player, monster, almost any entity +// and take control of their movement through space. Players are optionally allowed to look around +// while bound. This class was necessary to abstract all the physics differences away from the +// objects doing the binding. +//========================================================================== + +const idEventDef EV_BindAttach("bindattach", "ed"); +const idEventDef EV_BindDetach("binddrop", NULL); +const idEventDef EV_BindAttachBody("bindattachbody", "edd"); + +CLASS_DECLARATION(idEntity, hhBindController) + EVENT( EV_BindAttach, hhBindController::Event_Attach ) + EVENT( EV_BindDetach, hhBindController::Event_Detach ) + EVENT( EV_BindAttachBody, hhBindController::Event_AttachBody ) +END_CLASS + + +void hhBindController::Spawn() { + boundRider = NULL; + hand = NULL; + yawLimit = 180.0f; // no limit + float tension = spawnArgs.GetFloat("tension", "0.01"); + const char *anim = spawnArgs.GetString("boundanim"); + const char *hand = spawnArgs.GetString("def_hand"); + float yaw = spawnArgs.GetFloat("yawlimit", "180"); + float pitch = spawnArgs.GetFloat("pitchlimit", "75"); + bOrientPlayer = spawnArgs.GetBool("orientplayer"); + + bLooseBind = false; + + SetRiderParameters(anim, hand, yaw, pitch); + SetTension(tension); + hangID = 0; +} + +hhBindController::~hhBindController() { + Detach(); +} + +void hhBindController::Save(idSaveGame *savefile) const { + boundRider.Save(savefile); + savefile->WriteObject( hand ); + savefile->WriteBool( bLooseBind ); + savefile->WriteBool( bOrientPlayer ); + savefile->WriteStaticObject( force ); + savefile->WriteString( animationName ); + savefile->WriteString( handName ); + savefile->WriteFloat( yawLimit ); + savefile->WriteFloat(pitchLimit); + savefile->WriteInt( hangID ); +} + +void hhBindController::Restore( idRestoreGame *savefile ) { + boundRider.Restore(savefile); + savefile->ReadObject( reinterpret_cast(hand) ); + savefile->ReadBool( bLooseBind ); + savefile->ReadBool( bOrientPlayer ); + savefile->ReadStaticObject( force ); + savefile->ReadString( animationName ); + savefile->ReadString( handName ); + savefile->ReadFloat( yawLimit ); + savefile->ReadFloat(pitchLimit); + savefile->ReadInt( hangID ); +} + +idEntity *hhBindController::GetRider() const { + // Since the entity could be deleted at any time, we get the rider from the force (if used) + if (bLooseBind) { + //TODO: Make this force use a safe entityptr too + return force.GetEntity(); + } + return boundRider.GetEntity(); +} + +void hhBindController::SetTension(float tension) { + force.SetRestoreTime((1.0f - tension)*5.0f); + force.SetRestoreFactor(tension * g_springConstant.GetFloat()); +} + +void hhBindController::SetSlack(float slack) { + force.SetRestoreSlack(slack); +} + +void hhBindController::Think() { + idEntity::Think(); //TODO: This needed? + + if (bLooseBind && GetRider()) { + idVec3 loc = GetOrigin(); + force.SetTarget(loc); + + //TODO: Cull hangID out of this class if not needed. + + + // FIXME: Why is this needed? (Took out --pdm, test crane/rail before removing) +/* + idEntity *rider = GetRider(); + if (rider->IsType(idAFEntity_Base::Type)) { // Update the offset depending on if actor is ragdoll + idAFEntity_Base *af = static_cast(rider); + if (af->IsActiveAF()) { + force.SetEntity(rider, hangID, vec3_origin); + } + else { // non-ragdolled rider + if (rider->IsType(idActor::Type)) { + force.SetEntity(rider, 0, af->EyeOffset()); + } + else { + force.SetEntity(rider, 0, rider->GetOrigin()); + } + } + } +*/ + force.Evaluate(gameLocal.time); + } + else if (GetRider() && GetRider()->IsType(hhPlayer::Type) && OrientPlayer()) { + // This to insure that oriented players don't get out of sync + hhPlayer *player = static_cast(GetRider()); + idAngles angles = player->GetUntransformedViewAngles(); + player->SetOrientation(GetOrigin(), GetAxis(), angles.ToMat3()[0], angles); + } +} + +// Create our invisible hand to get rid of the weapon +void hhBindController::CreateHand(hhPlayer *player) { + if (gameLocal.isClient) { //rww + return; + } + + if (handName.Length()) { + ReleaseHand(); + + hand = hhHand::AddHand( player, handName.c_str() ); + } +} + +void hhBindController::ReleaseHand() { + if (hand) { + hand->RemoveHand(); + hand = NULL; + } +} + +void hhBindController::SetRiderParameters(const char *animname, const char *handname, float yaw, float pitch) { + animationName = animname; + handName = handname; + yawLimit = yaw; + pitchLimit = pitch; +} + +bool hhBindController::ValidRider(idEntity *ent) const { + return (ent != NULL); +} + +void hhBindController::Attach(idEntity *ent, bool loose, int bodyID, idVec3 &point) { + hhPlayer *player = ent->IsType(hhPlayer::Type) ? static_cast(ent) : NULL; + + if (GetRider()==NULL && ValidRider(ent)) { + + bLooseBind = loose; + + if (bLooseBind) { + idVec3 loc = GetOrigin(); + force.SetEntity(ent, bodyID, point); + force.SetTarget(loc); + if (ent->spawnArgs.GetBool("stable_tractor", "0")) { + force.SetAxisEntity(this); + } else { + force.SetAxisEntity(NULL); + } + BecomeActive(TH_THINK); + } + else { + boundRider = ent; + boundRider->SetOrigin(GetOrigin()); + + if (player && bOrientPlayer) { + idAngles angles = player->GetUntransformedViewAngles(); + player->SetOrientation(GetOrigin(), GetAxis(), angles.ToMat3()[0], angles); + } + else { + boundRider->SetAxis(GetAxis()); + } + boundRider->Bind(this, false); + } + + // Apply player parameters + if (player && !gameLocal.isMultiplayer) { //rww - in mp, don't want to restrict players when bound + player->maxRelativeYaw = yawLimit; + player->maxRelativePitch = pitchLimit; + player->bClampYaw = yawLimit < 180.0f; + CreateHand(player); +// player->hhweapon->Hide(); + } + // Play animation on actors (but not players in mp -rww) + if (ent->IsType(idActor::Type) && !ent->fl.tooHeavyForTractor && (!gameLocal.isMultiplayer || !ent->IsType(hhPlayer::Type))) { + idActor *actor = static_cast(ent); + actor->AI_BOUND = true; + if ( actor->GetHealth() > 0 ) { + actor->ProcessEvent(&AI_SetAnimPrefix, animationName.c_str()); + actor->SetAnimState( ANIMCHANNEL_LEGS, "Legs_Bound", 4 ); + actor->SetAnimState( ANIMCHANNEL_TORSO, "Torso_Bound", 4 ); + } + actor->BecameBound(this); + } + + hangID = 0; + if (ent->IsType(idAFEntity_Base::Type)) { + hangID = static_cast(ent)->spawnArgs.GetInt("hangID"); + } + } +} + +void hhBindController::Detach() { + idEntity *rider = GetRider(); + hhPlayer *player = rider && rider->IsType(hhPlayer::Type) ? static_cast(rider) : NULL; + if (rider) { + if (player) { + player->bClampYaw = false; + + if (gameLocal.isMultiplayer) { //do not fiddle with the player's anims in MP when he is grabbed, let him act normally + // Release player animation + player->AI_BOUND = false; + player->SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + player->SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + } + + ReleaseHand(); + } + if (rider->IsType(idActor::Type)) { + idActor *actor = static_cast(rider); + actor->AI_BOUND = false; + actor->BecameUnbound(this); + } + + if (bLooseBind) { + force.SetEntity(NULL); + } + else { + rider->Unbind(); + rider->SetAxis(mat3_identity); + if (player && bOrientPlayer) { + // Set everything back to normal at current view angles + idAngles angles = player->GetUntransformedViewAngles(); + player->SetOrientation(GetOrigin(), mat3_identity, angles.ToMat3()[0], angles); + } + boundRider = NULL; + } + } +} + +idVec3 hhBindController::GetRiderBindPoint() const { + idEntity* rider = GetRider(); + + if( !rider ) { + return vec3_origin; + } + + if( rider->IsType(idActor::Type) ) { + idActor *actor = static_cast( rider ); + if( actor->IsActiveAF() ) { + return actor->GetOrigin( GetHangID() ); + } else { + return actor->GetEyePosition(); + } + } + + return rider->GetOrigin(); +} + +void hhBindController::Event_Attach(idEntity *rider, bool loose) { + Attach(rider, loose); +} + +void hhBindController::Event_AttachBody(idEntity *rider, bool loose, int bodyID) { + Attach(rider, loose, bodyID); +} + +void hhBindController::Event_Detach() { + Detach(); +} + diff --git a/neo/prey/Prey/game_bindController.h b/neo/prey/Prey/game_bindController.h new file mode 100644 index 00000000..e450cb58 --- /dev/null +++ b/neo/prey/Prey/game_bindController.h @@ -0,0 +1,68 @@ + +#ifndef __HH_GAME_BINDCONTROLLER_H__ +#define __HH_GAME_BINDCONTROLLER_H__ + + +/* +=================================================================================== + + hhBindController + + Entity used to control an entity's position in space. + Used for rail rides, tractor beams, crane, etc. + +=================================================================================== +*/ + +extern const idEventDef EV_BindAttach; +extern const idEventDef EV_BindDetach; + +class hhBindController : public idEntity { + +public: + CLASS_PROTOTYPE( hhBindController ); + + void Spawn(); + virtual ~hhBindController(); + virtual void Think(); + virtual void Attach(idEntity *ent, bool loose=false, int bodyID=0, idVec3 &point=vec3_origin); + virtual void Detach(); + void SetTension(float tension); + void SetSlack(float slack); + void SetRiderParameters(const char *animname, const char *handname, float yaw, float pitch); + idVec3 GetRiderBindPoint() const; + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool OrientPlayer() const { return bOrientPlayer; } + idEntity * GetRider() const; + ID_INLINE bool IsLoose() const { return bLooseBind; } + ID_INLINE int GetHangID() const { return hangID; } + ID_INLINE void SetShuttle(bool shuttle) { force.SetShuttle(shuttle); } + +protected: + bool ValidRider(idEntity *ent) const; + void CreateHand(hhPlayer *player); + void ReleaseHand(); + + void Event_Attach(idEntity *rider, bool loose); + void Event_AttachBody(idEntity *rider, bool loose, int bodyID); + void Event_Detach(); + +protected: + idEntityPtr boundRider; // Pointer to rider for true bind-based riders + // Loose bindings store rider in the force + hhHand * hand; + bool bLooseBind; // True if the bind is through a force + bool bOrientPlayer; // Orient the player to my orientation + hhForce_Converge force; // Force used for "loose" binding + + // Apply these to any entities bound + idStr animationName; // Animation to play on riders + idStr handName; // Hand to apply to player riders + float yawLimit; // Yaw restriction for player riders + float pitchLimit; //rww - used for slabs and other things to restrict pitch of rider + int hangID; // Body id to attach to for hanging +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_blackjack.cpp b/neo/prey/Prey/game_blackjack.cpp new file mode 100644 index 00000000..5a168e5a --- /dev/null +++ b/neo/prey/Prey/game_blackjack.cpp @@ -0,0 +1,508 @@ +// game_blackjack +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +enum { + BJRESULT_BUST=0, + BJRESULT_PUSH=1, + BJRESULT_WIN=2, + BJRESULT_LOSE=3, + BJRESULT_BLACKJACK=4, + BJRESULT_5CARD=5 +}; + +const idEventDef EV_Deal("Deal", NULL); +const idEventDef EV_Hit("Hit", NULL); +const idEventDef EV_Stay("Stay", NULL); +const idEventDef EV_Double("Double", NULL); + +CLASS_DECLARATION(hhConsole, hhBlackJack) + EVENT( EV_Deal, hhBlackJack::Event_Deal) + EVENT( EV_Hit, hhBlackJack::Event_Hit) + EVENT( EV_Stay, hhBlackJack::Event_Stay) + EVENT( EV_Double, hhBlackJack::Event_Double) + EVENT( EV_UpdateView, hhBlackJack::Event_UpdateView) +END_CLASS + + +void hhBlackJack::Spawn() { + + Reset(); +} + +void hhBlackJack::Reset() { + bCanDeal = 1; + bCanIncBet = 1; + bCanDecBet = 1; + bCanHit = 0; + bCanStay = 0; + bCanDouble = 0; + bCanSplit = 0; + PlayerBet = Bet = 1; + DealerScore = PlayerScore = 0; + DealerAces = PlayerAces = 0; + PlayerCredits = spawnArgs.GetInt("credits"); + victoryAmount = spawnArgs.GetInt("victory"); + resultIndex = -1; + creditsWon = 0; + PlayerHand.Clear(); + DealerHand.Clear(); + + UpdateView(); +} + +void hhBlackJack::Save(idSaveGame *savefile) const { + int i; + + savefile->WriteInt( PlayerHand.Num() ); // Saving of idList + for( i = 0; i < PlayerHand.Num(); i++ ) { + savefile->Write(&PlayerHand[i], sizeof(card_t)); + } + savefile->WriteInt( DealerHand.Num() ); // Saving of idList + for( i = 0; i < DealerHand.Num(); i++ ) { + savefile->Write(&DealerHand[i], sizeof(card_t)); + } + + savefile->WriteInt(Bet); + savefile->WriteInt(PlayerBet); + savefile->WriteInt(DealerScore); + savefile->WriteInt(PlayerScore); + savefile->WriteInt(DealerAces); + savefile->WriteInt(PlayerAces); + savefile->WriteInt(PlayerCredits); + savefile->WriteInt(victoryAmount); + savefile->WriteInt(resultIndex); + savefile->WriteBool(bCanDeal); + savefile->WriteBool(bCanIncBet); + savefile->WriteBool(bCanDecBet); + savefile->WriteBool(bCanHit); + savefile->WriteBool(bCanStay); + savefile->WriteBool(bCanDouble); + savefile->WriteBool(bCanSplit); + savefile->WriteInt(creditsWon); +} + +void hhBlackJack::Restore( idRestoreGame *savefile ) { + int i, num; + + PlayerHand.Clear(); + savefile->ReadInt( num ); + PlayerHand.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->Read(&PlayerHand[i], sizeof(card_t)); + } + + DealerHand.Clear(); + savefile->ReadInt( num ); + DealerHand.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->Read(&DealerHand[i], sizeof(card_t)); + } + + savefile->ReadInt(Bet); + savefile->ReadInt(PlayerBet); + savefile->ReadInt(DealerScore); + savefile->ReadInt(PlayerScore); + savefile->ReadInt(DealerAces); + savefile->ReadInt(PlayerAces); + savefile->ReadInt(PlayerCredits); + savefile->ReadInt(victoryAmount); + savefile->ReadInt(resultIndex); + savefile->ReadBool(bCanDeal); + savefile->ReadBool(bCanIncBet); + savefile->ReadBool(bCanDecBet); + savefile->ReadBool(bCanHit); + savefile->ReadBool(bCanStay); + savefile->ReadBool(bCanDouble); + savefile->ReadBool(bCanSplit); + savefile->ReadInt(creditsWon); +} + +card_t hhBlackJack::GetCard(bool visible) +{ + card_t card; + char names[] = { '2','3','4','5','6','7','8','9','T','J','Q','K','A'}; + int values[] = { 2 , 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11}; + + int r = gameLocal.random.RandomInt(13); + card.Name = names[r]; + card.Value = values[r]; + card.Suit = gameLocal.random.RandomInt(4); + card.Visible = visible; + return card; +} + +void hhBlackJack::Deal() +{ + // Empty hands + PlayerHand.Clear(); + DealerHand.Clear(); + + Bet = PlayerBet; + creditsWon = 0; + + // Deal initial hands + PlayerHand.Append(GetCard(1)); + DealerHand.Append(GetCard(0)); + PlayerHand.Append(GetCard(1)); + DealerHand.Append(GetCard(1)); + + RetallyScores(); + + bCanIncBet = false; + bCanDecBet = false; + bCanDeal = false; + DeterminePlayCommands(); + resultIndex = -1; + + if (PlayerScore == 21) { + AssessScores(); + EndGame(); + } + + UpdateView(); +} + +void hhBlackJack::Hit() +{ + PlayerHand.Append(GetCard(1)); + RetallyScores(); + DeterminePlayCommands(); + + if (PlayerScore >= 21 || PlayerHand.Num() == 5) { + FollowDealerRules(); + AssessScores(); + EndGame(); + } + + UpdateView(); +} + +void hhBlackJack::Stay() +{ + FollowDealerRules(); + AssessScores(); + EndGame(); + UpdateView(); +} + +void hhBlackJack::Double() +{ + Bet *= 2; + PlayerHand.Append(GetCard(1)); + RetallyScores(); + DeterminePlayCommands(); + + // Force player to stay + FollowDealerRules(); + AssessScores(); + EndGame(); + + UpdateView(); +} + +void hhBlackJack::IncBet() { + + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + int oldBet = PlayerBet; + PlayerBet = idMath::ClampInt(PlayerBet, PlayerCredits, PlayerBet+amount); + PlayerBet = idMath::ClampInt(0, 999999, PlayerBet); + Bet = PlayerBet; + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + + UpdateView(); +} + +void hhBlackJack::DecBet() { + + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + int oldBet = PlayerBet; + if (PlayerBet > amount) { + PlayerBet -= amount; + } + else if (PlayerBet > 1) { + PlayerBet = 1; + } + Bet = PlayerBet; + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + + UpdateView(); +} + + +/* + Blackjack utility functions +*/ + +void hhBlackJack::RetallyScores() +{ + int ix; + PlayerScore = DealerScore = PlayerAces = DealerAces = 0; + + for (ix=0; ix 21 && PlayerAces) { + PlayerScore -= 10; + PlayerAces--; + } + for (ix=0; ix 21 && DealerAces) { + DealerScore -= 10; + DealerAces--; + } +} + +void hhBlackJack::AssessScores() +{ + if (PlayerScore > 21) { + creditsWon = -Bet; + resultIndex = BJRESULT_BUST; + } + else if (DealerScore > 21) { + creditsWon = Bet; + resultIndex = BJRESULT_WIN; + } + else if (PlayerScore == 21 && PlayerHand.Num() == 2) { + // BlackJack + creditsWon = Bet * 2; + resultIndex = BJRESULT_BLACKJACK; + } + else if (PlayerScore <= 21 && PlayerHand.Num() == 5) { + creditsWon = Bet * 5; + resultIndex = BJRESULT_5CARD; + } + else if (DealerScore > PlayerScore) { + creditsWon = -Bet; + resultIndex = BJRESULT_LOSE; + } + else if (PlayerScore > DealerScore) { + creditsWon = Bet; + resultIndex = BJRESULT_WIN; + } + else { + // Push + creditsWon = 0; + resultIndex = BJRESULT_PUSH; + } + + PlayerCredits += creditsWon; + PlayerCredits = idMath::ClampInt(0, 999999999, PlayerCredits); + Bet = PlayerBet; + + // Play victory/failure sound + if (victoryAmount && PlayerCredits >= victoryAmount) { + StartSound( "snd_victory", SND_CHANNEL_ANY, 0, true, NULL ); + ActivateTargets( gameLocal.GetLocalPlayer() ); + victoryAmount = 0; + } + else if (creditsWon > 0) { + StartSound( "snd_win", SND_CHANNEL_ANY, 0, true, NULL ); + } + else if (creditsWon < 0) { + StartSound( "snd_lose", SND_CHANNEL_ANY, 0, true, NULL ); + } +} + +void hhBlackJack::UpdateBetMechanics() { + bCanIncBet = PlayerCredits > PlayerBet; + bCanDecBet = PlayerBet > 0; + if (PlayerCredits < PlayerBet) { + PlayerBet = PlayerCredits; + } +} + +void hhBlackJack::EndGame() { + bCanDeal = 1; + UpdateBetMechanics(); + bCanHit = 0; + bCanStay = 0; + bCanDouble = 0; + bCanSplit = 0; +} + +void hhBlackJack::UpdateView() { + // UpdateView() is posted as an event because sometimes we're already nested down in the gui handling code when it is called + // and it in turn re-enters the gui handling code with HandleNamedEvent() + CancelEvents(&EV_UpdateView); + PostEventMS(&EV_UpdateView, 0); +} + +void hhBlackJack::Event_UpdateView() { + int ix; + bool bGameOver = false; + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + if (PlayerCredits <= 0) { + bCanIncBet = bCanDecBet = bCanHit = bCanStay = bCanDouble = bCanSplit = bCanDeal = false; + bGameOver = true; + } + + gui->SetStateBool("bgameover", bGameOver); + gui->SetStateBool("bcanincbet", bCanIncBet); + gui->SetStateBool("bcandecbet", bCanDecBet); + gui->SetStateBool("bcanhit", bCanHit); + gui->SetStateBool("bcanstay", bCanStay); + gui->SetStateBool("bcandouble", bCanDouble); + gui->SetStateBool("bcansplit", bCanSplit); + gui->SetStateBool("bcandeal", bCanDeal); + gui->SetStateInt("credits", PlayerCredits); + gui->SetStateInt("currentbet", Bet); + gui->SetStateInt("result", resultIndex); + gui->SetStateInt("creditswon", creditsWon); + + // Clear + for (ix=0; ix<6; ix++) { + gui->SetStateInt(va("Dealer%d_Visible", ix+1), 0); + gui->SetStateInt(va("Player%d_Visible", ix+1), 0); + gui->SetStateBool(va("Dealer%d_Flipped", ix+1), 0); + gui->SetStateBool(va("Player%d_Flipped", ix+1), 0); + } + + // Show dealer hand + for (ix=0; ixSetStateInt(va("Dealer%d_Visible", ix+1), 1); + if (DealerHand[ix].Visible) { + gui->SetStateBool(va("Dealer%d_Flipped", ix+1), 1); + gui->SetStateInt(va("Dealer%d_Suit", ix+1), DealerHand[ix].Suit); + char cardChar = DealerHand[ix].Name; + if (cardChar == 'T') { + gui->SetStateString(va("Dealer%d_Card", ix+1), "10"); + } + else { + gui->SetStateString(va("Dealer%d_Card", ix+1), va("%c", cardChar)); + } + gui->SetStateInt(va("Dealer%d_Red", ix+1), DealerHand[ix].Suit==SUIT_HEARTS || DealerHand[ix].Suit==SUIT_DIAMONDS ? 1 : 0); + } + } + + // Show player hand + for (ix=0; ixSetStateInt(va("Player%d_Visible", ix+1), 1); + if (PlayerHand[ix].Visible) { + gui->SetStateBool(va("Player%d_Flipped", ix+1), 1); + gui->SetStateInt(va("Player%d_Suit", ix+1), PlayerHand[ix].Suit); + char cardChar = PlayerHand[ix].Name; + if (cardChar == 'T') { + gui->SetStateString(va("Player%d_Card", ix+1), "10"); + } + else { + gui->SetStateString(va("Player%d_Card", ix+1), va("%c", cardChar)); + } + gui->SetStateInt(va("Player%d_Red", ix+1), PlayerHand[ix].Suit==SUIT_HEARTS || PlayerHand[ix].Suit==SUIT_DIAMONDS ? 1 : 0); + } + } + + gui->StateChanged(gameLocal.time, true); + CallNamedEvent("Update"); + } +} + +void hhBlackJack::FollowDealerRules() { + // Flip up cards + for (int i=0; iReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("deal") == 0) { + Deal(); + } + else if (token.Icmp("hit") == 0) { + Hit(); + } + else if (token.Icmp("stay") == 0) { + Stay(); + } + else if (token.Icmp("double") == 0) { + Double(); + } + else if (token.Icmp("incbet") == 0) { + IncBet(); + } + else if (token.Icmp("decbet") == 0) { + DecBet(); + } + else if (token.Icmp("reset") == 0) { + Reset(); + } + else if (token.Icmp("restart") == 0) { + PlayerCredits = spawnArgs.GetInt("credits"); + Bet = PlayerBet = 1; + EndGame(); + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhBlackJack::Event_Deal() { + Deal(); +} + +void hhBlackJack::Event_Hit() { + Hit(); +} + +void hhBlackJack::Event_Stay() { + Stay(); +} + +void hhBlackJack::Event_Double() { + Double(); +} diff --git a/neo/prey/Prey/game_blackjack.h b/neo/prey/Prey/game_blackjack.h new file mode 100644 index 00000000..8deb162c --- /dev/null +++ b/neo/prey/Prey/game_blackjack.h @@ -0,0 +1,70 @@ + +#ifndef __GAME_BLACKJACK_H__ +#define __GAME_BLACKJACK_H__ + +extern const idEventDef EV_Deal; + +typedef struct card_s { + int Suit; + int Value; + char Name; + unsigned char Visible; +}card_t; + +class hhBlackJack : public hhConsole { +public: + CLASS_PROTOTYPE( hhBlackJack ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Deal(); + void Hit(); + void Stay(); + void Double(); + void IncBet(); + void DecBet(); + + void Reset(); + card_t GetCard(bool visible); + void RetallyScores(); + void AssessScores(); + void EndGame(); + void UpdateView(); + void FollowDealerRules(); + void DeterminePlayCommands(); + void UpdateBetMechanics(); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void Event_Deal(); + void Event_Hit(); + void Event_Stay(); + void Event_Double(); + void Event_UpdateView(); + +private: + idList PlayerHand; + idList DealerHand; + + int Bet; + int PlayerBet; + int DealerScore, PlayerScore; + int DealerAces, PlayerAces; + int PlayerCredits; + int victoryAmount; + int resultIndex; + int creditsWon; + + bool bCanDeal; + bool bCanIncBet; + bool bCanDecBet; + bool bCanHit; + bool bCanStay; + bool bCanDouble; + bool bCanSplit; +}; + + +#endif /* __GAME_BLACKJACK_H__ */ diff --git a/neo/prey/Prey/game_cards.cpp b/neo/prey/Prey/game_cards.cpp new file mode 100644 index 00000000..2747ddca --- /dev/null +++ b/neo/prey/Prey/game_cards.cpp @@ -0,0 +1,130 @@ +// game_cards.cpp +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +static char cardnames[] = { '2','3','4','5','6','7','8','9','T','J','Q','K','A' }; +static char suitnames[] = { 'D','H','S','C' }; + + +//============================================================== +// hhCard utility class +//============================================================== +hhCard::hhCard() { + suit = SUIT_SPADES; + value = CARD_ACE; +} + +hhCard::hhCard(int value, int suit) { + this->suit = suit; + this->value = value; +} + +int hhCard::operator==(const hhCard &other) const { + return value==other.Value() && suit==other.Suit(); +} + +int hhCard::Value() const { + return value; +} + +int hhCard::Suit() const { + return suit; +} + +char hhCard::ValueName() { + return cardnames[value]; +} + +char hhCard::SuitName() { + return suitnames[suit]; +} + + +//============================================================== +// hhDeck utility class +//============================================================== + +CLASS_DECLARATION(idClass, hhDeck) +END_CLASS + +hhDeck::hhDeck() { + Generate(); + Shuffle(); +} + +void hhDeck::Save(idSaveGame *savefile) const { + int i; + savefile->WriteInt( cards.Num() ); // hhStack + for (i=0; iWrite(&cards[i], sizeof(hhCard)); + } +} + +void hhDeck::Restore( idRestoreGame *savefile ) { + int i, num; + hhCard card; + + cards.Clear(); // hhStack + savefile->ReadInt( num ); + cards.SetNum( num ); + for (i=0; iRead(&card, sizeof(hhCard)); + cards[i] = card; + } +} + +void hhDeck::Generate() { + int value, suit; + cards.Clear(); + for (value=0; value cards; + }; + +#endif /* __GAME_CARDS_H__ */ diff --git a/neo/prey/Prey/game_cilia.cpp b/neo/prey/Prey/game_cilia.cpp new file mode 100644 index 00000000..2cf2cda8 --- /dev/null +++ b/neo/prey/Prey/game_cilia.cpp @@ -0,0 +1,300 @@ +//************************************************************************** +//** +//** GAME_CILIA.CPP +//** +//** Specific type of SpherePart. When shot, they retract for a while, +//** and trigger nearby cilia to retract as well. +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +const idEventDef EV_StickOut( "stickout", NULL ); +const idEventDef EV_TriggerNearby( "triggernearby", NULL ); +const idEventDef EV_Idle( "idle", NULL ); + +CLASS_DECLARATION( hhSpherePart, hhSphereCilia ) + EVENT( EV_Activate, hhSphereCilia::Event_Trigger ) + EVENT( EV_TriggerNearby, hhSphereCilia::Event_TriggerNearby ) + EVENT( EV_StickOut, hhSphereCilia::Event_StickOut ) + EVENT( EV_Idle, hhSphereCilia::Event_Idle ) // JRM + EVENT( EV_Touch, hhSphereCilia::Event_Touch ) +END_CLASS + +// STATE DECLARATIONS ------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSphereCilia::Spawn +// +//========================================================================== + +void hhSphereCilia::Spawn(void) { + fl.takedamage = true; // Allow the spherepart to be damaged + fl.allowSpiritWalkTouch = true; + bRetracted = false; // Start sticking out + bAlreadyActivated = false; // Hasn't been touched yet + + //HUMANHEAD jsh PCF 4/26/06 allow creatures to trigger cilia + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); + + idleAnim = GetAnimator()->GetAnim("idle"); + pullInAnim = GetAnimator()->GetAnim("pullin"); + stickOutAnim = GetAnimator()->GetAnim("stickout"); + + spawnArgs.GetFloat( "nearbySize", "128", nearbySize ); + spawnArgs.GetFloat( "idleDelay", "-1", idleDelay ); + + if( idleDelay < 0 ) { // negative number denotes a random start delay + idleDelay = gameLocal.random.RandomFloat(); + } + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time + idleDelay * 1000, 100 ); +} + +void hhSphereCilia::Save(idSaveGame *savefile) const { + + savefile->WriteInt( pullInAnim ); + savefile->WriteInt( stickOutAnim ); + savefile->WriteFloat( nearbySize ); + savefile->WriteFloat( idleDelay ); + savefile->WriteBool( bRetracted ); + savefile->WriteBool( bAlreadyActivated ); +} + +void hhSphereCilia::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( pullInAnim ); + savefile->ReadInt( stickOutAnim ); + savefile->ReadFloat( nearbySize ); + savefile->ReadFloat( idleDelay ); + savefile->ReadBool( bRetracted ); + savefile->ReadBool( bAlreadyActivated ); +} + +//========================================================================== +// +// hhSphereCilia::Damage +// +// Currently similar to +//========================================================================== + +void hhSphereCilia::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + hhFxInfo fxInfo; + + if( bRetracted ) { // Don't trigger if already retracted + return; + } + + bRetracted = true; + + // Remove collision on the model + GetPhysics()->SetContents(0); + + // Switch to the broken model + SetModel( spawnArgs.GetString( "model_broken", "" ) ); + + // Play an explode sound + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, true, NULL ); + + fl.takedamage = false; + fl.applyDamageEffects = false; // Cilia don't accept damage wounds, since they swap out models + + PostEventSec( &EV_TriggerNearby, 0.2f + gameLocal.random.RandomFloat() * 0.1 ); + + ApplyEffect(); +} + +//========================================================================== +// +// hhSphereCilia::Event_Touch +// +//========================================================================== +void hhSphereCilia::Event_Touch( idEntity *other, trace_t *trace ) { + Trigger( other ); +} + +//========================================================================== +// +// hhSphereCilia::Trigger +// +//========================================================================== + +void hhSphereCilia::Trigger( idEntity *activator ) { + if( bRetracted || !activator ) { // Don't trigger if already retracted + return; + } + + bRetracted = true; + + // Remove collision on the model + GetPhysics()->SetContents(0); + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, pullInAnim, gameLocal.time, 100); + + StartSound( "snd_in", SND_CHANNEL_ANY, 0, true, NULL ); + + PostEventSec( &EV_TriggerNearby, 0.2f + gameLocal.random.RandomFloat() * 0.1 ); + PostEventSec( &EV_StickOut, 5.0f + idleDelay ); + + // Trigger this cilia's targets the first time it is touched + if ( !bAlreadyActivated ) { + ActivateTargets( activator ); + bAlreadyActivated = true; + } + + ApplyEffect(); +} + +//========================================================================== +// +// hhSphereCilia::Event_Trigger +// +//========================================================================== + +void hhSphereCilia::Event_Trigger( idEntity *activator ) { + Trigger( activator ); +} + +//========================================================================== +// +// hhSphereCilia::Event_TriggerNearby +// +// Trigger nearby cilia (to create a cascading retract effect) +//========================================================================== + +void hhSphereCilia::Event_TriggerNearby( void ) { + int i; + int e; + hhSphereCilia *ent; + idEntity *entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 org; + + org = GetPhysics()->GetOrigin(); + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = org[i] - nearbySize; + bounds[1][i] = org[i] + nearbySize; + } + + // Find the first closest neighbor cilia to trigger + numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) { + if( !entityList[e]->IsType( hhSphereCilia::Type ) ) { + continue; + } + + ent = static_cast< hhSphereCilia * >( entityList[e] ); + + if( ent->bRetracted ) { + continue; + } + + ent->Trigger( this ); + } +} + +//========================================================================== +// +// hhSphereCilia::Event_StickOut +// +// Return the cilia to its original state +//========================================================================== + +#define MAX_NEARBY_CILIA 8 + +void hhSphereCilia::Event_StickOut( void ) { + int i; + int num; + idEntity *touch[MAX_NEARBY_CILIA]; + idEntity *hit; + bool canFit; + + // Check if the cilia can fit when sticking out + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), -1, touch, MAX_NEARBY_CILIA ); + + canFit = true; + for( i = 0; i < num; i++ ) { + hit = touch[ i ]; + assert( hit ); + + if(( hit == this )) { + continue; + } + + // Hit an entity, so reset the cilia to attempt to emerge in a bit + PostEventSec( &EV_StickOut, 5.0f ); + return; + } + + //HUMANHEAD jsh PCF 4/26/06 allow creatures to trigger cilia + // Restore the proper collision + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); + + // Play the stick out anims and blend the idle back in + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, stickOutAnim, gameLocal.time, 100); + PostEventMS(&EV_Idle, 100); // JRM + + StartSound( "snd_out", SND_CHANNEL_ANY, 0, true, NULL); + + bRetracted = false; +} + +// +// Event_Idle +// +// JRM +void hhSphereCilia::Event_Idle(void) { + + assert(idleAnim); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 200); +}; + +void hhSphereCilia::ApplyEffect( void ) { + hhFxInfo fxInfo; + fxInfo.SetNormal( GetAxis()[0] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_explode", GetOrigin() + (GetAxis()[0] * 16.0f), GetAxis(), &fxInfo ); + + int num; + int i; + idEntity* ents[MAX_GENTITIES]; + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX|CONTENTS_RENDERMODEL, ents, MAX_GENTITIES ); + + const char *damageType = spawnArgs.GetString("damageType", "damage_cilia"); + for( i = 0; i < num; i++ ) { + if( ents[ i ] != this && !ents[ i ]->IsType(hhSphereCilia::Type) && !ents[ i ]->IsType(idAFAttachment::Type) ) { + ents[ i ]->Damage( this, this, vec3_origin, damageType, 1.0f, INVALID_JOINT ); + } + } + +} + diff --git a/neo/prey/Prey/game_cilia.h b/neo/prey/Prey/game_cilia.h new file mode 100644 index 00000000..cb2d795b --- /dev/null +++ b/neo/prey/Prey/game_cilia.h @@ -0,0 +1,36 @@ + +#ifndef __GAME_CILIA_H +#define __GAME_CILIA_H + +extern const idEventDef EV_TriggerNearby; +extern const idEventDef EV_StickOut; + +class hhSphereCilia : public hhSpherePart { +public: + CLASS_PROTOTYPE( hhSphereCilia ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Trigger( idEntity *activator ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_TriggerNearby( void ); + void Event_StickOut( void ); + void Event_Idle( void ); // JRM + void Event_Touch( idEntity *other, trace_t *trace ); + + void ApplyEffect( void ); + +protected: + int pullInAnim; + int stickOutAnim; + float nearbySize; + float idleDelay; + bool bRetracted; + bool bAlreadyActivated; +}; + +#endif /* __GAME_CILIA_H */ diff --git a/neo/prey/Prey/game_console.cpp b/neo/prey/Prey/game_console.cpp new file mode 100644 index 00000000..02bbad47 --- /dev/null +++ b/neo/prey/Prey/game_console.cpp @@ -0,0 +1,822 @@ +// Game_console.cpp +// + +// Container class for all world objects that have GUIs on them. (except monsters & weapons) +// Provides some keys to these guis not provided by default GUI code like "rand" + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define TRANSLATION_TIME 1000 // Time over which translation takes place + +const idEventDef EV_CallGuiEvent("guiEvent", "s"); + +//========================================================================== +// +// hhConsole +// +//========================================================================== + +CLASS_DECLARATION(idStaticEntity, hhConsole) + EVENT( EV_Activate, hhConsole::Event_Activate ) + EVENT( EV_TalonAction, hhConsole::Event_TalonAction ) + EVENT( EV_CallGuiEvent, hhConsole::Event_CallGuiEvent ) + EVENT( EV_BecomeNonSolid, hhConsole::Event_BecomeNonSolid ) + EVENT( EV_PostSpawn, hhConsole::Event_PostSpawn ) +END_CLASS + +void hhConsole::Spawn() { +// BecomeActive(TH_MISC2); // For ai + + bTimeEventsAlways = spawnArgs.GetBool("timeEventsAlways"); + bUsesRand = spawnArgs.GetBool("usesRand"); + if (bUsesRand || bTimeEventsAlways) { + BecomeActive(TH_THINK); + } + + bool bDamageable = !spawnArgs.GetBool("noDamage"); + if (bDamageable) { + fl.takedamage = true; + } + + // Stuff color into gui_parms 20,21,22 + idVec3 color; + GetColor(color); + SetOnAllGuis("gui_parm20", color[0]); + SetOnAllGuis("gui_parm21", color[1]); + SetOnAllGuis("gui_parm22", color[2]); + + // Translation variables + translationAlpha.Init(gameLocal.time, 0, 0.0f, 0.0f); // Start at 0 (untranslated) + transState = TS_UNTRANSLATED; + + // Start idle sound + StartSound("snd_idle", SND_CHANNEL_ANY, 0, true, NULL); + + UpdateVisuals(); + + // Init AI data + aiCanUse = spawnArgs.GetBool("ai_can_use"); + aiUseCount = 0; + aiReuseWaitTime = (int)(spawnArgs.GetFloat("ai_reuse_wait")*1000.0f); + aiUseTime = (int)(spawnArgs.GetFloat("ai_use_time")*1000.0f); + aiTriggerWaitTime = (int)(spawnArgs.GetFloat("ai_use_trigger_wait")*1000.0f); + aiMaxUses = spawnArgs.GetInt("ai_max_uses", "-1"); + aiRetriggerWait = (int)(spawnArgs.GetFloat("ai_use_retrigger_wait")*1000.0f); + + // Clamp the trigger wait to be less than the total use time + if(aiTriggerWaitTime > aiUseTime) { + aiTriggerWaitTime = aiUseTime - 32; + if(aiTriggerWaitTime < 0) { + aiTriggerWaitTime = 0; + } + } + + aiCurrUsedStartTime = gameLocal.GetTime(); + aiCurrUsedBy = NULL; + aiLastUsedBy = NULL; + aiLastUsedTime = -aiReuseWaitTime; + aiWaitingToTrigger = true; + aiLastTriggerTime = gameLocal.GetTime() - 1000; + + perchSpot = NULL; + PostEventMS( &EV_PostSpawn, 0 ); +} + +void hhConsole::Event_PostSpawn() { + // Automatically cause all consoles to spawn Talon perch spots + if(!spawnArgs.GetBool("noTalonTarget")) { + idDict args; + idVec3 offset; + bool bGuiInteractive = false; + + // cjr - Iterate through guis and determine if any are interactive. If all are not, then Talon should not squawk + for ( int ix = 0; ix < MAX_RENDERENTITY_GUI; ix++ ) { + if ( renderEntity.gui[ix] && renderEntity.gui[ix]->IsInteractive() ) { + bGuiInteractive = true; + } + } + + const char *perchDef; + if ( spawnArgs.GetBool( "noTalonSquawk" ) || !bGuiInteractive) { // CJR: Check if talon should squawk at this spot + perchDef = spawnArgs.GetString("def_perch"); + } else { + perchDef = spawnArgs.GetString("def_perchSquawk"); // Talon should squawk at this spot + } + + if (!gameLocal.isClient) { + if (perchDef && perchDef[0]) { + // Set the offset for the perch location and priority + offset = GetPhysics()->GetAxis() * spawnArgs.GetVector("offset_perch", "0 0 24"); + float offsetYaw = spawnArgs.GetFloat("offset_perchyaw"); + args.SetVector("origin", GetPhysics()->GetOrigin() + offset); + + idAngles angles = GetAxis().ToAngles(); + angles.yaw += offsetYaw; + args.SetMatrix("rotation", angles.ToMat3()); + args.Set("target", this->name.c_str()); + perchSpot = (hhTalonTarget *)gameLocal.SpawnObject( perchDef, &args ); // Consoles are automatically high priority spots + if (perchSpot && ( spawnArgs.GetBool("bindPerchSpot") || IsBound() ) ) { + perchSpot->Bind(this, true); + } + } + else { + gameLocal.Warning("Need def_perch key on %s", name.c_str()); + perchSpot = NULL; + } + } + } +} + +void hhConsole::Save(idSaveGame *savefile) const { + savefile->WriteFloat( translationAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( translationAlpha.GetDuration() ); + savefile->WriteFloat( translationAlpha.GetStartValue() ); + savefile->WriteFloat( translationAlpha.GetEndValue() ); + + savefile->Write( &transState, sizeof(transState) ); + savefile->WriteBool( bTimeEventsAlways ); + savefile->WriteBool( bUsesRand ); + savefile->WriteObject( perchSpot ); + savefile->WriteBool( aiCanUse ); + savefile->WriteInt( aiUseCount ); + savefile->WriteInt( aiMaxUses ); + savefile->WriteInt( aiReuseWaitTime ); + savefile->WriteInt( aiUseTime ); + savefile->WriteInt( aiTriggerWaitTime ); + savefile->WriteBool( aiWaitingToTrigger ); + savefile->WriteInt( aiLastTriggerTime ); + savefile->WriteInt( aiRetriggerWait ); + aiCurrUsedBy.Save(savefile); + savefile->WriteInt( aiCurrUsedStartTime ); + aiLastUsedBy.Save(savefile); + savefile->WriteInt( aiLastUsedTime ); +} + +void hhConsole::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + translationAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + translationAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + translationAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + translationAlpha.SetEndValue( set ); + + savefile->Read( &transState, sizeof(transState) ); + savefile->ReadBool( bTimeEventsAlways ); + savefile->ReadBool( bUsesRand ); + savefile->ReadObject( reinterpret_cast(perchSpot) ); + savefile->ReadBool( aiCanUse ); + savefile->ReadInt( aiUseCount ); + savefile->ReadInt( aiMaxUses ); + savefile->ReadInt( aiReuseWaitTime ); + savefile->ReadInt( aiUseTime ); + savefile->ReadInt( aiTriggerWaitTime ); + savefile->ReadBool( aiWaitingToTrigger ); + savefile->ReadInt( aiLastTriggerTime ); + savefile->ReadInt( aiRetriggerWait ); + + aiCurrUsedBy.Restore(savefile); + savefile->ReadInt( aiCurrUsedStartTime ); + aiLastUsedBy.Restore(savefile); + savefile->ReadInt( aiLastUsedTime ); +} + +void hhConsole::Event_CallGuiEvent(const char *eventName) { + CallNamedEvent(eventName); +} + +void hhConsole::Event_BecomeNonSolid( void ) { + GetPhysics()->GetClipModel()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); +} + +void hhConsole::SetOnAllGuis(const char *key, float value) { + for (int ix=0; ixSetStateFloat(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::SetOnAllGuis(const char *key, int value) { + for (int ix=0; ixSetStateInt(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::SetOnAllGuis(const char *key, bool value) { + for (int ix=0; ixSetStateBool(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::SetOnAllGuis(const char *key, const char *value) { + for (int ix=0; ixSetStateString(key, value); + renderEntity.gui[ix]->StateChanged(gameLocal.time); + } + } +} + +void hhConsole::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + + fl.takedamage = false; + + // JRM - disable msgs on kill? + if(spawnArgs.GetBool("ai_damage_disable_msgs","0")) { + fl.refreshReactions = false; + } + + // Inform the gui that we are damaged (not used, since gui materials can't see entity parms) +// SetShaderParm(SHADERPARM_DIVERSITY, gameLocal.random.RandomFloat()); + SetOnAllGuis("damaged", true); + + // Tell the gui that we died, so the gui can be in sync + CallNamedEvent("Death"); +} + +bool hhConsole::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + idToken token; + if (!src->ReadToken(&token) || token == ";") { + return false; + } + if (token.Icmp("setfont") == 0) { + if (src->ReadToken(&token)) { + idStr fontname = "fonts\\"; + fontname += token; + if (renderEntity.gui[0]) { + // renderEntity.gui[0]->Translate(fontname.c_str()); // jmarshall + } + } + return true; + } + + src->UnreadToken( &token ); + return false; +} + +// This is overridden so we can update camera targets reguardless of PVS +void hhConsole::Present( void ) { + PROFILE_SCOPE("Present", PROFMASK_NORMAL); + + if ( !gameLocal.isNewFrame ) { + 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 + if ( cameraTarget ) { // && gameLocal.InPlayerPVS( this ) ) { // HUMANHEAD pdm: removed PVS check + 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 ); + } +} + +void hhConsole::Think() { + + if (thinkFlags & TH_THINK) { + if (bUsesRand) { + float rand = gameLocal.random.RandomFloat(); + SetOnAllGuis("rand", rand); + } + + if (bTimeEventsAlways) { + // Call event handler so time events get run + const char *cmd; + sysEvent_t ev; + memset( &ev, 0, sizeof( ev ) ); + ev.evType = SE_NONE; + + // This should work after the merge is done, since in the new code, HandleEvent() calls RunTimeEvents() + for (int ix=0; ixHandleEvent(&ev, gameLocal.time); + HandleGuiCommands(this, cmd); + } + } + } + } + + if (thinkFlags & TH_MISC1) { + // Handle any translation + if (transState == TS_TRANSLATING || transState == TS_UNTRANSLATING) { + float alpha = translationAlpha.GetCurrentValue(gameLocal.time); + if (renderEntity.gui[0] || renderEntity.gui[1] || renderEntity.gui[2]) { + + SetOnAllGuis("translationAlpha", alpha); + + // Determine if the transition is done + if (translationAlpha.IsDone(gameLocal.time)) { + if (transState == TS_TRANSLATING) { + transState = TS_TRANSLATED; + BecomeInactive(TH_MISC1); + } + else if (transState == TS_UNTRANSLATING) { + transState = TS_UNTRANSLATED; + BecomeInactive(TH_MISC1); + } + } + } + } + } + + // JRM - ai using update + if (thinkFlags & TH_MISC2) { + UpdateUse(); + } + + idStaticEntity::Think(); +} + +// Toggles the translation state of the gui +void hhConsole::Translate(bool bLanded) { + + if (!spawnArgs.GetBool("translate")) { + return; + } + + float curalpha = translationAlpha.GetCurrentValue(gameLocal.time); + switch(transState) { + case TS_UNTRANSLATED: + case TS_UNTRANSLATING: + translationAlpha.Init(gameLocal.time, TRANSLATION_TIME, curalpha, 1.0f); + transState = TS_TRANSLATING; + BecomeActive(TH_MISC1); + break; + case TS_TRANSLATED: + case TS_TRANSLATING: + translationAlpha.Init(gameLocal.time, TRANSLATION_TIME, curalpha, 0.0f); + transState = TS_UNTRANSLATING; + BecomeActive(TH_MISC1); + break; + } + + if (bLanded) { + StartSound( "snd_translate", SND_CHANNEL_ANY, 0, true, NULL); + } +} + +void hhConsole::ConsoleActivated() { + // Called when someone "uses" the gui (gui calls activate cmd) + // JRM - turn off msgs after the player uses this + if(!aiCurrUsedBy.IsValid() && spawnArgs.GetBool("ai_player_use_disable_msgs", "0")) { + fl.refreshReactions = false; + } +} + +void hhConsole::ClearTalonTargetType() { // CJR - Called when any command is sent to a gui. This disables talon's squawking + if ( perchSpot ) { + perchSpot->SetSquawk( false ); // Set the console to stop Talon from squawking + } +} + +void hhConsole::Event_Activate( idEntity *activator ) { + // Don't toggle on/off like in idStaticEntity + // any GUIs still get the onTrigger() event +} + +void hhConsole::Event_TalonAction(idEntity *talon, bool landed) { + Translate(landed); +} + +void hhConsole::Use(idAI *ai) { + HH_ASSERT(ai); + + // Just started using console? + if(!aiCurrUsedBy.IsValid()) { + aiCurrUsedBy = ai; + aiCurrUsedStartTime = gameLocal.GetTime(); + BecomeActive(TH_MISC2); + } +} + +void hhConsole::UpdateUse(void) { + + // Time to deactivate? + if(aiCurrUsedBy.IsValid() && gameLocal.GetTime() > aiCurrUsedStartTime + aiUseTime) { + aiLastUsedBy = aiCurrUsedBy; + aiLastUsedTime = gameLocal.GetTime(); + aiCurrUsedBy = NULL; + aiWaitingToTrigger = true; + aiUseCount++; + if(aiMaxUses > 0 && aiUseCount >= aiMaxUses) { + fl.refreshReactions = false; + } + BecomeInactive(TH_MISC2); + } + + // Time to fire triggers? + if (aiCurrUsedBy.IsValid()) { + + // Waiting to trigger... + if(aiWaitingToTrigger && gameLocal.GetTime() > aiCurrUsedStartTime + aiTriggerWaitTime) { + aiWaitingToTrigger = false; + + OnTriggeredByAI(aiCurrUsedBy.GetEntity()); + } + // Time to re-trigger? + else if (!aiWaitingToTrigger && aiRetriggerWait > 0 && gameLocal.GetTime() - aiLastTriggerTime > aiRetriggerWait) { + OnTriggeredByAI(aiCurrUsedBy.GetEntity()); + } + } +} + +void hhConsole::OnTriggeredByAI(idAI *ai) { + + aiLastTriggerTime = gameLocal.GetTime(); + + // Tell the gui that we were used, so the gui can be in sync + CallNamedEvent("AIUse"); +} + +bool hhConsole::CanUse(idAI *ai) { + HH_ASSERT(ai); + + if (!aiCanUse) { + return false; + } + + if (aiMaxUses > 0 && aiUseCount >= aiMaxUses) { + return false; + } + + // Can't use it if someone else is using it + if (aiCurrUsedBy.IsValid() && aiCurrUsedBy.GetEntity() != ai) { + return false; + } + + // Have to wait before we can use it again? + if (gameLocal.GetTime() < aiLastUsedTime + aiReuseWaitTime) { + return false; + } + + return ai->spawnArgs.GetBool("CanUseConsole", "0"); +} + +//========================================================================== +// +// hhConsoleCountdown +// +//========================================================================== + +const idEventDef EV_CountdownStart("startcountdown", NULL); +const idEventDef EV_CountdownStop("stopcountdown", NULL); +const idEventDef EV_CountdownSet("setcountdown", "d"); + +CLASS_DECLARATION(hhConsole, hhConsoleCountdown) + EVENT( EV_Activate, hhConsoleCountdown::Event_Activate ) + EVENT( EV_CountdownStart, hhConsoleCountdown::Event_StartCountdown ) + EVENT( EV_CountdownStop, hhConsoleCountdown::Event_StopCountdown ) + EVENT( EV_CountdownSet, hhConsoleCountdown::Event_SetCountdown ) +END_CLASS + +void hhConsoleCountdown::Spawn() { + countingDown = false; + countStart = spawnArgs.GetFloat("countStart"); + countEnd = spawnArgs.GetFloat("countEnd"); + countdown.Init(gameLocal.time, 0, countStart, countStart); + SetGuiOctal((int)countStart); + + if (spawnArgs.GetBool("enabled")) { + StartCountdown(); + } +} + +void hhConsoleCountdown::Save(idSaveGame *savefile) const { + savefile->WriteBool(countingDown); + savefile->WriteFloat(countStart); + savefile->WriteFloat(countEnd); + + savefile->WriteFloat( countdown.GetStartTime() ); // idInterpolate + savefile->WriteFloat( countdown.GetDuration() ); + savefile->WriteFloat( countdown.GetStartValue() ); + savefile->WriteFloat( countdown.GetEndValue() ); +} + +void hhConsoleCountdown::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadBool(countingDown); + savefile->ReadFloat(countStart); + savefile->ReadFloat(countEnd); + + savefile->ReadFloat( set ); // idInterpolate + countdown.SetStartTime( set ); + savefile->ReadFloat( set ); + countdown.SetDuration( set ); + savefile->ReadFloat( set ); + countdown.SetStartValue(set); + savefile->ReadFloat( set ); + countdown.SetEndValue( set ); +} + +void hhConsoleCountdown::SetGuiOctal(int value) { + // Convert to base-8 numbers and set on gui + idStr octalValue; + sprintf(octalValue, "%o", value); + SetOnAllGuis("countdown", octalValue.c_str()); +} + +void hhConsoleCountdown::UpdateGUI(float curValue) { + SetGuiOctal((int)curValue); + SetOnAllGuis("fraction", curValue / countStart); + SetOnAllGuis("counting", countingDown); +} + +void hhConsoleCountdown::Think() { + hhConsole::Think(); + + if (thinkFlags & TH_MISC3) { + if (countingDown) { + UpdateGUI(countdown.GetCurrentValue(gameLocal.time)); + if (countdown.IsDone(gameLocal.time)) { + BecomeInactive(TH_MISC3); + ActivateTargets(this); + StopCountdown(); + } + } + } +} + +void hhConsoleCountdown::SetCountdown(int count) { + if (countingDown) { + gameLocal.Warning("You must stop countdown before set"); + return; + } + countStart = count; + countdown.Init(gameLocal.time, 0, countStart, countStart); + UpdateGUI(count); +} + +void hhConsoleCountdown::StartCountdown() { + if (!countingDown) { + // Start counting down from current value + float curValue = countdown.GetCurrentValue(gameLocal.time); + // resume from previously stopped count + countdown.Init(gameLocal.time, SEC2MS(countStart), curValue, countEnd); + BecomeActive(TH_MISC3); + countingDown = true; + } +} + +void hhConsoleCountdown::StopCountdown() { + if (countingDown) { + // Stop countdown at current value + float curValue = countdown.GetCurrentValue(gameLocal.time); + countdown.Init(gameLocal.time, 0, curValue, curValue); + countingDown = false; + UpdateGUI(countdown.GetCurrentValue(gameLocal.time)); + } +} + +void hhConsoleCountdown::Reset() { + // Reset to initial start time (or set start time) + countdown.Init(gameLocal.time, 0, countStart, countStart); + UpdateGUI(countdown.GetCurrentValue(gameLocal.time)); +} + +void hhConsoleCountdown::Event_Activate(idEntity *activator) { + StartCountdown(); +} +void hhConsoleCountdown::Event_SetCountdown(int count) { + SetCountdown(count); +} +void hhConsoleCountdown::Event_StartCountdown() { + StartCountdown(); +} +void hhConsoleCountdown::Event_StopCountdown() { + StopCountdown(); +} + + +//========================================================================== +// +// hhConsoleKeypad +// +// Generic console for entering text on a keypad and displaying it +//========================================================================== + +CLASS_DECLARATION(hhConsole, hhConsoleKeypad) +END_CLASS + +void hhConsoleKeypad::Spawn() { + maxLength = spawnArgs.GetInt("maxlength"); + + SetOnAllGuis("atmax", false); +} + +void hhConsoleKeypad::Save(idSaveGame *savefile) const { + savefile->WriteString(keyBuffer); + savefile->WriteInt(maxLength); +} + +void hhConsoleKeypad::Restore( idRestoreGame *savefile ) { + savefile->ReadString(keyBuffer); + savefile->ReadInt(maxLength); +} + +void hhConsoleKeypad::UpdateGui() { + int length = keyBuffer.Length(); + + // Enforce maxlength + if (length > maxLength) { + keyBuffer = keyBuffer.Left(maxLength); + } + + SetOnAllGuis("atmax", (length >= maxLength)); + SetOnAllGuis("keypad", keyBuffer.c_str()); +} + +bool hhConsoleKeypad::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + idToken token; + if (!src->ReadToken(&token) || token == ";") { + return false; + } + if (token.Icmp("backspace") == 0) { + if (keyBuffer.Length() > 0) { + keyBuffer = keyBuffer.Left(keyBuffer.Length()-1); + } + } + else if (token.Icmp("clear") == 0) { + keyBuffer.Empty(); + } + else if (token.Icmp("enter") == 0) { + keyBuffer += '\n'; + } + else if (token.IcmpPrefix("keypad_") == 0) { // KEYPAD_X + token.ToLower(); + token.Strip("keypad_"); + keyBuffer += token.c_str(); + } + else { + return false; + } + + UpdateGui(); + return true; +} + + +//========================================================================== +// +// hhConsoleAlarm +// +// alarm console +//========================================================================== + +const idEventDef EV_SpawnMonster(""); + +CLASS_DECLARATION(hhConsole, hhConsoleAlarm) + EVENT( EV_SpawnMonster, hhConsoleAlarm::Event_SpawnMonster ) +END_CLASS + +void hhConsoleAlarm::Spawn() { + bAlarmActive = false; + numMonsters = 0; + bSpawning = false; + maxMonsters = spawnArgs.GetInt( "max_monsters", "0" ); + BecomeActive(TH_TICKER); +} + +void hhConsoleAlarm::Save(idSaveGame *savefile) const { + savefile->WriteBool(bAlarmActive); + savefile->WriteInt( numMonsters ); + savefile->WriteInt( maxMonsters ); + currentMonster.Save( savefile ); + savefile->WriteBool( bSpawning ); +} + +void hhConsoleAlarm::Restore( idRestoreGame *savefile ) { + savefile->ReadBool(bAlarmActive); + savefile->ReadInt( numMonsters ); + savefile->ReadInt( maxMonsters ); + currentMonster.Restore( savefile ); + savefile->ReadBool( bSpawning ); +} + +void hhConsoleAlarm::ConsoleActivated() { + bAlarmActive = !bAlarmActive; + if ( !bAlarmActive ) { + CancelEvents( &EV_SpawnMonster ); + for ( int i=0;iIsType( hhMountedGun::Type ) ) { + targets[i]->PostEventMS( &EV_Deactivate, 0 ); + } + targets[i]->DeactivateTargetsType( hhMountedGun::Type ); + } + } + } +} + +void hhConsoleAlarm::Event_SpawnMonster() { + idDict args; + // Copy keys for monster + idStr tmpStr, realKeyName; + const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL); + while(kv) { + tmpStr = kv->GetKey(); + int usIndex = tmpStr.FindChar("ent_", '_'); + realKeyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + args.Set(realKeyName, kv->GetValue()); + kv = spawnArgs.MatchPrefix("ent_", kv); + } + idEntity *ent = gameLocal.FindEntity( spawnArgs.GetString( "spawn_location" ) ); + if ( ent ) { + args.SetVector( "origin", ent->GetOrigin() ); + } else { + gameLocal.Warning("No spawn_location specified for %s\n", GetName()); + return; + } + + // entity collision checks for seeing if we are going to collide with another entity on spawn + const idDict *entDef = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_monster" ), false ); + if ( entDef ) { + bool clipped = false; + idVec3 entSize; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + entDef->GetVector( "size", "0", entSize ); + idBounds bounds = idBounds( ent->GetOrigin() ).Expand( max( entSize.x, entSize.y ) ); + int num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 5000 ); + } + for ( int i=0;iIsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage || !hit->IsType( idActor::Type ) ) { + continue; + } + clipped = true; + break; + } + if ( !clipped ) { + currentMonster = gameLocal.SpawnObject(spawnArgs.GetString( "def_monster" ), &args); + if ( currentMonster.IsValid() ) { + currentMonster->PostEventMS( &EV_Activate, 0, this ); + bSpawning = false; + numMonsters++; + } else { + gameLocal.Warning( "%s could not spawn monster\n", GetName() ); + } + } + } +} + +void hhConsoleAlarm::Ticker() { + + if (bAlarmActive) { + if ( currentMonster.IsValid() && currentMonster->GetHealth() <= 0 ) { + currentMonster.Clear(); + } + + //check on our monster + if ( !bSpawning && !currentMonster.IsValid() && spawnArgs.GetString( "def_monster" )[0] ) { + if ( !maxMonsters || numMonsters < maxMonsters ) { + float min = spawnArgs.GetFloat( "mindelay", "2.0" ); + float max = spawnArgs.GetFloat( "maxdelay", "2.0" ); + if ( max < min ) { + max = min; + } + bSpawning = true; + PostEventSec( &EV_SpawnMonster, min + (max-min)*gameLocal.random.RandomFloat() ); + } + } + } + + hhConsole::Ticker(); +} \ No newline at end of file diff --git a/neo/prey/Prey/game_console.h b/neo/prey/Prey/game_console.h new file mode 100644 index 00000000..1af75001 --- /dev/null +++ b/neo/prey/Prey/game_console.h @@ -0,0 +1,148 @@ + +#ifndef __GAME_CONSOLE_H__ +#define __GAME_CONSOLE_H__ + +extern const idEventDef EV_CallGuiEvent; + +enum ETranslationState { + TS_TRANSLATING, + TS_TRANSLATED, + TS_UNTRANSLATING, + TS_UNTRANSLATED +}; + +class hhTalonTarget; // CJR + +class hhConsole : public idStaticEntity { +public: + CLASS_PROTOTYPE( hhConsole ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think(); + virtual void ConsoleActivated(); + virtual void ClearTalonTargetType(); // CJR - Called when any command is sent to a gui. This disables talon's squawking + + virtual void Present( void ); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + void Translate(bool bLanded); + void SetOnAllGuis(const char *key, float value); + void SetOnAllGuis(const char *key, int value); + void SetOnAllGuis(const char *key, bool value); + void SetOnAllGuis(const char *key, const char *value); + virtual void PlayerControls(usercmd_t *cmd) {} + + virtual void Use(idAI *ai); // JRM - An AI is starting to use this console + bool CanUse(idAI *ai); // JRM - Returns TRUE if the given AI is allowed to use this console right now + int GetLastUsedTime(void) {return aiLastUsedTime;} + idAI* GetLastUsedAI(void) {return aiLastUsedBy.GetEntity();} + virtual void OnTriggeredByAI(idAI *ai); // JRM - Called when the AI actually "presses" the console + void UpdateUse(void); // Called every tick + +protected: + void Event_Activate(idEntity *activator); + void Event_TalonAction(idEntity *talon, bool landed); + void Event_CallGuiEvent(const char *eventName); + void Event_BecomeNonSolid( void ); + void Event_PostSpawn(); + +protected: + idInterpolate translationAlpha; + ETranslationState transState; + bool bTimeEventsAlways; + bool bUsesRand; + + hhTalonTarget *perchSpot; // Associated Talon perch spot with this console + + // JRM - AI data + bool aiCanUse; // True if the AI is allowed to use this console + int aiUseCount; // Number of times AI has used this console + int aiMaxUses; // Max number of times the ai can use this console + int aiReuseWaitTime; // Time the AI has to wait before they can use this console again (ms) + int aiUseTime; // How long once AI starts using this console, do they stay here? (total ms) + int aiTriggerWaitTime; // How long once AI starts using this console until we fire this console's trigger? (ms) + bool aiWaitingToTrigger; // TRUE if the ai is waiting to fire the triggers for this console + int aiLastTriggerTime; // The time we last triggered this console + int aiRetriggerWait; // -1 = Never retrigger, otherwise time (ms) that we should retrigger while using + + idEntityPtr aiCurrUsedBy; // The AI is currently being using this console + int aiCurrUsedStartTime; // The time the current user STARTED using this console + + idEntityPtr aiLastUsedBy; // The AI that last used this console + int aiLastUsedTime; // The time that aiLastUsedBy used finished using this console + +}; + +class hhConsoleCountdown : public hhConsole { + CLASS_PROTOTYPE( hhConsoleCountdown ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + void Reset(); + void UpdateGUI(float curValue); + void SetGuiOctal(int value); + void SetCountdown(int count); + void StartCountdown(); + void StopCountdown(); + +protected: + void Event_Activate(idEntity *activator); + void Event_SetCountdown(int count); + void Event_StartCountdown(); + void Event_StopCountdown(); + +protected: + bool countingDown; // Whether we are running or not + float countStart; // Starting value of countdown + float countEnd; // Ending value of countdown + idInterpolate countdown; // Interpolator that always carries the current count +}; + + +class hhConsoleKeypad : public hhConsole { + CLASS_PROTOTYPE( hhConsoleKeypad ); + +public: + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void UpdateGui(); + +protected: + idStr keyBuffer; + int maxLength; +}; + + +class hhConsoleAlarm : public hhConsole { + CLASS_PROTOTYPE( hhConsoleAlarm ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void ConsoleActivated(); + virtual void Ticker(); +protected: + void Event_SpawnMonster(); + idEntityPtr currentMonster; + int maxMonsters; + int numMonsters; + bool bSpawning; +private: + bool bAlarmActive; +}; + + +#endif /* __GAME_CONSOLE_H__ */ diff --git a/neo/prey/Prey/game_damagetester.cpp b/neo/prey/Prey/game_damagetester.cpp new file mode 100644 index 00000000..39c37ea2 --- /dev/null +++ b/neo/prey/Prey/game_damagetester.cpp @@ -0,0 +1,180 @@ +//************************************************************************** +//** +//** GAME_DAMAGETESTER.CPP +//** +//** +//** This object is used to calculate the damage done to it over time +//** When initially damaged, it calculates the amount of damage done for a +//** given amount of time +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" +#include "game_damagetester.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +const idEventDef EV_ResetTarget( "", NULL ); +const idEventDef EV_CheckRemove( "", NULL ); + +CLASS_DECLARATION( hhAnimatedEntity, hhDamageTester ) + EVENT( EV_ResetTarget, hhDamageTester::Event_ResetTarget ) + EVENT( EV_CheckRemove, hhDamageTester::Event_CheckRemove ) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhDamageTester::Spawn +// +//========================================================================== + +void hhDamageTester::Spawn(void) { + fl.takedamage = true; + gameLocal.Printf("\"Name\", \"Damage (Melee)\", \"Damage (Close)\", \"Damage (Medium)\", \"Damage (Far)\", \ + \"Hits (Melee)\", \"Hits (Close)\", \"Hits (Medium)\", \"Hits (Far)\"\n"); + + testTime = spawnArgs.GetFloat( "testTime", "5" ); + if( testTime < 1 ) { + testTime = 1; + } + + // Get the distances and reset values + for( int i = 0; i < DD_MAX; i++ ) { + distance[i] = spawnArgs.GetVector( va("distance%d", i), "0 0 0" ); + totalDamage[i] = 0; + hitCount[i] = 0; + } + + // Get the name of the weapon (useful for outputting to an Excel-friendly format) + weaponName = spawnArgs.GetString( "weaponName", "None" ); + + // Store original location and axis + originalLocation = GetPhysics()->GetOrigin(); + originalAxis = GetPhysics()->GetAxis(); + + // Initialize the target + targetIndex = -1; + PostEventMS( &EV_ResetTarget, 0 ); + + GetPhysics()->SetContents( CONTENTS_SOLID ); +} + +//========================================================================== +// +// hhDamageTester::~hhDamageTester +// +//========================================================================== + +hhDamageTester::~hhDamageTester() { + int i; + + gameLocal.Printf( "\nDAMAGE TEST RESULTS:\n" ); + + // Weapon Name + gameLocal.Printf( "\"%s\"", weaponName); + + // Damage/Sec for each distance + for( i = 0; i < DD_MAX; i++ ) { + gameLocal.Printf(", %.1f", totalDamage[i] / testTime ); + } + + // Hits/Sec for each distance + for( i = 0; i < DD_MAX; i++ ) { + gameLocal.Printf(", %.1f", hitCount[i] / testTime ); + } + + gameLocal.Printf("\n\n"); +} + +//========================================================================== +// +// hhDamageTester::Damage +// +//========================================================================== + +void hhDamageTester::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( targetIndex < 0 || targetIndex >= DD_MAX ) { + return; + } + + if( bUndamaged ) { // First time this object has been hit + bUndamaged = false; + PostEventSec( &EV_ResetTarget, testTime ); + } + + // Obtain damage + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "hhDamageTester::Damage: Unknown damageDef '%s'", damageDefName ); + return; + } + + int damage = damageDef->dict.GetInt( "damage", "0" ); + + totalDamage[targetIndex] += damage * damageScale; + hitCount[targetIndex]++; + + return; +} + +//========================================================================== +// +// hhDamageTester::Event_ResetTarget +// +//========================================================================== + +void hhDamageTester::Event_ResetTarget( void ) { + // Reset the target to undamaged + bUndamaged = true; + health = 999999; // Ensure that it will never die + + targetIndex++; + if( targetIndex >= DD_MAX ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // Set the location of the target + GetPhysics()->SetOrigin( originalLocation + distance[targetIndex] ); + UpdateVisuals(); + + PostEventSec( &EV_CheckRemove, testTime ); // Checks to autoremove the entity after a certain time +} + +//========================================================================== +// +// hhDamageTester::Event_CheckRemove +// +// Resets the target if a certain period of inactivity has occured (only if +// the weapon was unable to hit the target, for instance the wrench) +// Only happens if the target has not been hit +//========================================================================== + +void hhDamageTester::Event_CheckRemove( void ) { + if( bUndamaged && targetIndex > 0 ) { + PostEventMS( &EV_ResetTarget, 0 ); + } +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/game_damagetester.h b/neo/prey/Prey/game_damagetester.h new file mode 100644 index 00000000..98316890 --- /dev/null +++ b/neo/prey/Prey/game_damagetester.h @@ -0,0 +1,39 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __GAME_DAMAGETESTER_H__ +#define __GAME_DAMAGETESTER_H__ + +typedef enum hhDamageDist_s { + DD_MELEE, + DD_CLOSE, + DD_MEDIUM, + DD_FAR, + DD_MAX +} hhDamageDist_t; + +class hhDamageTester : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhDamageTester ); + + ~hhDamageTester(); + void Spawn(void); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void Event_ResetTarget( void ); + void Event_CheckRemove( void ); + +protected: + + bool bUndamaged; + int targetIndex; + float testTime; + + idVec3 originalLocation; + idMat3 originalAxis; + + int totalDamage[DD_MAX]; + int hitCount[DD_MAX]; + idVec3 distance[DD_MAX]; // Relatives distances for each target location (relative to axis) + const char *weaponName; +}; + +#endif /* __GAME_DAMAGETESTER_H__ */ +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/game_dda.cpp b/neo/prey/Prey/game_dda.cpp new file mode 100644 index 00000000..c9d0ca5e --- /dev/null +++ b/neo/prey/Prey/game_dda.cpp @@ -0,0 +1,681 @@ +/* +=============================================================================== +game_dda.cpp + + This contains the generic functionality for the dynamic difficulty adjustment system, + as well as a statistic tracking system. +=============================================================================== +*/ + +// HEADER FILES --------------------------------------------------------------- + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//============================================================================= +// +// hhDDAManager::hhDDAManager +// +//============================================================================= + +hhDDAManager::hhDDAManager() { + bForcedDifficulty = false; + difficulty = 0.5f; + + ClearTracking(); +} + +//============================================================================= +// +// hhDDAManager::~hhDDAManager +// +//============================================================================= + +hhDDAManager::~hhDDAManager() { + ClearTracking(); +} + +//============================================================================= +// +// hhDDAManager::Save +// +//============================================================================= + +void hhDDAManager::Save(idSaveGame *savefile) const { + int i; + int num; + + savefile->WriteBool( bForcedDifficulty ); + savefile->WriteFloat( difficulty ); + + for ( i = 0; i < DDA_INDEX_MAX; i++ ) { + ddaProbability[i].Save( savefile ); + } + + savefile->WriteStringList( locationNameData ); + savefile->WriteStringList( locationData ); + + num = healthData.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteInt( healthData[i] ); + } + + savefile->WriteStringList( healthSpiritData ); + savefile->WriteStringList( ammoData ); + savefile->WriteStringList( miscData ); + + num = deathData.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteString( deathData[i].location ); + savefile->WriteInt( deathData[i].time ); + } +} + +//============================================================================= +// +// hhDDAManager::Restore +// +//============================================================================= + +void hhDDAManager::Restore( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadBool( bForcedDifficulty ); + savefile->ReadFloat( difficulty ); + + for ( i = 0; i < DDA_INDEX_MAX; i++ ) { + ddaProbability[i].Restore( savefile ); + } + + savefile->ReadStringList( locationNameData ); + savefile->ReadStringList( locationData ); + + savefile->ReadInt( num ); + healthData.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( healthData[i] ); + } + + savefile->ReadStringList( healthSpiritData ); + savefile->ReadStringList( ammoData ); + savefile->ReadStringList( miscData ); + + savefile->ReadInt( num ); + deathData.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadString( deathData[i].location ); + savefile->ReadInt( deathData[i].time ); + } +} + +//============================================================================= +// +// hhDDAManager::ClearTracking +// +//============================================================================= + +void hhDDAManager::ClearTracking() { + locationNameData.Clear(); + locationData.Clear(); + healthData.Clear(); + healthSpiritData.Clear(); + ammoData.Clear(); + miscData.Clear(); +} + +//============================================================================= +// +// hhDDAManager::GetDifficulty +// +//============================================================================= + +float hhDDAManager::GetDifficulty() { + return difficulty; +} + +//============================================================================= +// +// hhDDAManager::RecalculateDifficulty +// +// Recalculate overall difficulty from the individual creature difficulties +// +// Must be done after any damaged are added or deaths are recorded +//============================================================================= + +void hhDDAManager::RecalculateDifficulty( int updateFlags ) { + int count = 0; + float accum = 0.0f; + + float oldDiff = difficulty; // TEMP + + if ( g_wicked.GetBool() ) { // Wicked mode, force the DDA to 1.0 + difficulty = 1.0f; + return; + } + + if ( !g_useDDA.GetBool() ) { // Skip the DDA calculations. After g_wicked so that is still a valid mode + difficulty = 0.5f; + return; + } + + if ( bForcedDifficulty ) { // Don't recalculate if the difficulty is forced + return; + } + + for( int i = 0; i < DDA_INDEX_MAX; i++ ) { + if ( !ddaProbability[i].IsUsed() ) { // Ignore any difficulties that shouldn't count yet + continue; + } + + if ( (1 << i ) & updateFlags ) { // Only update the difficulty based upon the creatures currently attacking + accum += ddaProbability[i].GetIndividualDifficulty(); + count++; + } + } + + if ( count == 0 ) { // No individual difficulties, so return the default + difficulty = 0.5f; + } else { + difficulty = accum / count; + } + + if ( count && g_printDDA.GetBool() ) { + common->Printf( "difficulty: [%.2f]\n", difficulty ); + } +} + +//============================================================================= +// +// hhDDAManager::ForceDifficulty +// +// Force the difficulty to a given value -- used for debugging and Wicked mode +//============================================================================= + +void hhDDAManager::ForceDifficulty( float newDifficulty ) { + if ( newDifficulty > 1.0f ) { // Clamp the max + newDifficulty = 1.0f; + } else if ( newDifficulty < 0.0f ) { // negative difficulty values resets the difficulty + common->Printf( "Difficulty defaulted back to 0.5\n" ); + difficulty = 0.5f; + bForcedDifficulty = false; + return; + } + + bForcedDifficulty = true; + difficulty = newDifficulty; +} + +//============================================================================= +// +// hhDDAManager::DDA_Heartbeat +// +//============================================================================= + +void hhDDAManager::DDA_Heartbeat( hhPlayer* player ) { + idStr locText; + assert( player ); + + if ( !g_trackDDA.GetBool() || gameLocal.isMultiplayer ) { + return; + } + + ammo_t ammoType; + ammoType = idWeapon::GetAmmoNumForName( "ammo_rifle" ); + int ammo_rifle_count = player->inventory.ammo[ ammoType ]; + float ammo_rifle = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_sniper" ); + int ammo_sniper_count = player->inventory.ammo[ ammoType ]; + float ammo_sniper = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_crawler" ); + int ammo_crawler_count = player->inventory.ammo[ ammoType ]; + float ammo_crawler = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_autocannon" ); + int ammo_autocannon_count = player->inventory.ammo[ ammoType ]; + float ammo_autocannon = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_autocannon_grenade" ); + int ammo_autocannon_grenade_count = player->inventory.ammo[ ammoType ]; + float ammo_autocannon_grenade = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_acid" ); + int ammo_acid_count = player->inventory.ammo[ ammoType ]; + float ammo_acid = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_crawler_red" ); + int ammo_crawler_red_count = player->inventory.ammo[ ammoType ]; + float ammo_crawler_red = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + ammoType = idWeapon::GetAmmoNumForName( "ammo_energy" ); + int ammo_energy_count = player->inventory.ammo[ ammoType ]; + float ammo_energy = 100 * player->inventory.AmmoPercentage( player, ammoType ); + + + idStr location; + player->GetLocationText( location ); + + idVec3 playerOrigin = player->GetOrigin(); + + bool bTalonAttack = false; + if ( player->talon.IsValid() && player->talon->IsAttacking() ) { + bTalonAttack = true; + } + + locationNameData.Append( location ); + + locationData.Append( idStr( va( "%.2f %.2f %.2f", playerOrigin.x, playerOrigin.y, playerOrigin.z ) ) ); + + healthData.Append( player->GetHealth() ); + + healthSpiritData.Append( idStr( va( "%d, %d, %.2f, %.2f", player->GetHealth(), player->GetSpiritPower(), player->ddaProbabilityAccum, gameLocal.GetDDA()->GetDifficulty() ) ) ); + + ammoData.Append( idStr( va( "%d, %d, %d, %d, %d, %d, %d, %d, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f", + ammo_rifle_count, ammo_sniper_count, ammo_crawler_count, ammo_autocannon_count, ammo_autocannon_grenade_count, ammo_acid_count, ammo_crawler_red_count, ammo_energy_count, // Ammo totals + ammo_rifle, ammo_sniper, ammo_crawler, ammo_autocannon, ammo_autocannon_grenade, ammo_acid, ammo_crawler_red, ammo_energy ) ) ); // Ammo percentages + + miscData.Append( idStr( va( "%d, %d, %d, %d, %d", player->GetCurrentWeapon(), player->IsLighterOn(), bTalonAttack, player->IsSpiritOrDeathwalking(), player->ddaNumEnemies ) ) ); // currentWeapon, lighter, talon attacking, IsSpiritOrDeathwalking, numEnemies +} + +//============================================================================= +// +// hhDDAManager::DDA_AddDamage +// +//============================================================================= + +void hhDDAManager::DDA_AddDamage( int ddaIndex, int damage ) { + if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) { + common->Warning( "hhDDAManager::DDA_AddDamage: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX ); + ddaIndex = 0; + } + + ddaProbability[ddaIndex].AddDamage( damage * 2 ); +} + +//============================================================================= +// +// hhDDAManager::DDA_AddSurvivalHealth +// +//============================================================================= + +void hhDDAManager::DDA_AddSurvivalHealth( int ddaIndex, int health ) { + if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) { + common->Warning( "hhDDAManager::DDA_AddSurvivalHealth: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX ); + ddaIndex = 0; + } + + ddaProbability[ddaIndex].AddSurvivalValue( health ); + + // Adjust DDA test + float prob = DDA_GetProbability( ddaIndex, health ); + float probAdjust = ( prob - 0.5f ) * 0.05f; // Difficulty adjustment raises and lowers based upon how far the probability is from the dda level + + ddaProbability[ddaIndex].AdjustDifficulty( probAdjust ); +} + +//============================================================================= +// +// hhDDAManager::DDA_AddDeath +// +//============================================================================= + +void hhDDAManager::DDA_AddDeath( hhPlayer *player, idEntity *attacker ) { + hhMonsterAI *monster; + int ddaIndex; + + if ( g_trackDDA.GetBool() && !gameLocal.isMultiplayer ) { + ddaDeath_t death; + death.time = gameLocal.GetTime(); + player->GetLocationText( death.location ); + deathData.Append( death ); + } + + if ( !attacker->IsType( hhMonsterAI::Type ) ) { + return; + } + + monster = static_cast(attacker); + + if ( monster ) { + ddaIndex = monster->spawnArgs.GetInt( "ddaIndex", "0" ); + + if ( ddaIndex < 0 ) { // Player could be killed by monsters that shouldn't be included in the DDA + return; + } + + if ( ddaIndex >= DDA_INDEX_MAX ) { + ddaIndex = 0; + } + } + + ddaProbability[ddaIndex].AdjustDifficulty( -0.075f ); +} + +//============================================================================= +// +// hhDDAManager::DDA_GetProbability +// +// Return the probability that this index of creature will do enough damage to reduce the player's health to zero +//============================================================================= + +float hhDDAManager::DDA_GetProbability( int ddaIndex, int value ) { + if ( ddaIndex < 0 || ddaIndex >= DDA_INDEX_MAX ) { + common->Warning( "hhDDAManager::DDA_GetProbability: ddaIndex out of range %d [%d]\n", ddaIndex, DDA_INDEX_MAX ); + ddaIndex = 0; + } + + return ddaProbability[ddaIndex].GetProbability( value ); +} + +//============================================================================= +// +// hhDDAManager::Export +// +//============================================================================= + +void hhDDAManager::Export( const char* filename ) { + idStr ExportFile, ExportBase; + + if( !filename ) { + ExportBase = gameLocal.GetMapName(); + ExportBase.StripPath(); + ExportBase.StripFileExtension(); + ExportBase = va("%s_%s", cvarSystem->GetCVarString("win_username"), ExportBase.c_str() ); + ExportBase.DefaultPath( "statfiles/" ); + } + else { + ExportBase = va( "%s_", filename ); + ExportBase.StripFileExtension(); + ExportBase.DefaultPath( "statfiles/" ); + } + + gameLocal.Printf( "Exporting stats to csv files\n" ); + + // Export to .cvs files and to a .lin file + ExportDDAData( ExportBase, "Location Name, Health, Spirit Power, Survival Chance, DDA", "_HEALTH", healthSpiritData ); + + ExportDDAData( ExportBase, + "Location Name, Ammo Rifle, Ammo Sniper, Ammo Crawler, Ammo Autocannon, Ammo Grenade, Ammo Acid, Ammo Launcher, Ammo Energy, Ammo Rifle %, Ammo Sniper %, Ammo Crawler %, Ammo Autocannon %, Ammo Grenade %, Ammo Acid %, Ammo Launcher %, Ammo Energy %", + "_AMMO", ammoData ); + + ExportDDAData( ExportBase, "Location Name, Current Weapon, Lighter On, Talon Attacking, IsSpiritOrDeathwalking, Num Enemies", "_MISC", miscData ); + + ExportLINData( ExportBase ); +} + +//============================================================================= +// +// hhDDAManager::ExportDDAData +// +//============================================================================= + +void hhDDAManager::ExportDDAData( idStr fileName, const char* header, const char *fileAddition, idList data ) { + idFile* f = NULL; + + //Build the filename + fileName.Append( fileAddition ); + + fileName.DefaultFileExtension("csv"); + f = fileSystem->OpenFileWrite( fileName.c_str() ); + if( !f ) { + common->Warning( "Failed to open stat tracking file '%s'", fileName.c_str() ); + return; + } + + //Export the header for this file... + f->Printf( "%s\n", header ); + + for( int i = 0; i < data.Num(); i++ ) { + f->Printf( "%s, %s\n", locationNameData[i].c_str(), data[i].c_str() ); + } + + //Close the output file + fileSystem->CloseFile( f ); +} + +//============================================================================= +// +// hhDDAManager::ExportLINData +// +//============================================================================= + +void hhDDAManager::ExportLINData( idStr fileName ) { + idFile* f = NULL; + + //Build the filename + fileName.DefaultFileExtension("lin"); + f = fileSystem->OpenFileWrite( fileName.c_str() ); + if( !f ) { + common->Warning( "Failed to open stat tracking file '%s'", fileName.c_str() ); + return; + } + + for( int i = 0; i < locationData.Num(); i++ ) { + if ( healthData[i] > 0 ) { // Only store living health values in the .lin file + f->Printf( "%s\n", locationData[i].c_str() ); + } + } + + //Close the output file + fileSystem->CloseFile( f ); +} + +//============================================================================= +// +// hhDDAManager::PrintDDA +// +//============================================================================= + +void hhDDAManager::PrintDDA( void ) { + int i; + hhDDAProbability *prob; + + for( i = 0; i < DDA_INDEX_MAX; i++ ) { + prob = &ddaProbability[i]; + if ( !prob->IsUsed() ) { + continue; + } + + common->Printf( "DDA Index: %d\n", i ); + common->Printf( "Ind. Difficulty: %.2f\n", prob->GetIndividualDifficulty() ); +/* + common->Printf( "Last Damages: [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f] [%.1f]\n", + prob->damages[0], prob->damages[1], prob->damages[2], prob->damages[3], + prob->damages[4], prob->damages[5], prob->damages[6], prob->damages[7] ); +*/ + } +} + +//============================================================================= +// NEW DDA +//============================================================================= + +//============================================================================= +// +// hhDDAProbability::hhDDAProbability +// +//============================================================================= + +hhDDAProbability::hhDDAProbability() { + damageRover = 0; + survivalRover = 0; + + mean = 25.0f; + stdDeviation = 1.0f; + bUsed = false; + individualDifficulty = 0.5f; + + for( int i = 0; i < NUM_DAMAGES; i++ ) { + damages[i] = mean; // Initialize to a low, but reasonable value + survivalValues[i] = 0.5f; // Initialize to a middle value + } +} + +//============================================================================= +// +// hhDDAProbability::Save +// +//============================================================================= + +void hhDDAProbability::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteBool( bUsed ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->WriteInt( damages[i] ); + } + + savefile->WriteInt( damageRover ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->WriteFloat( survivalValues[i] ); + } + + savefile->WriteInt( survivalRover ); + + savefile->WriteFloat( mean ); + savefile->WriteFloat( stdDeviation ); + savefile->WriteFloat( individualDifficulty ); +} + +//============================================================================= +// +// hhDDAProbability::Restore +// +//============================================================================= + +void hhDDAProbability::Restore( idRestoreGame *savefile ) { + int i; + + savefile->ReadBool( bUsed ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->ReadInt( damages[i] ); + } + + savefile->ReadInt( damageRover ); + + for ( i = 0; i < NUM_DAMAGES; i++ ) { + savefile->ReadFloat( survivalValues[i] ); + } + + savefile->ReadInt( survivalRover ); + + savefile->ReadFloat( mean ); + savefile->ReadFloat( stdDeviation ); + savefile->ReadFloat( individualDifficulty ); +} + +//============================================================================= +// +// hhDDAProbability::AddDamage +// +//============================================================================= + +void hhDDAProbability::AddDamage( int damage ) { + // Add the damage to the list + damages[ damageRover ] = damage; + damageRover = ( damageRover + 1 ) % NUM_DAMAGES; + + bUsed = true; + + CalculateProbabilityCurve(); +} + +//============================================================================= +// +// hhDDAProbability::AddSurvivalValue +// +//============================================================================= + +void hhDDAProbability::AddSurvivalValue( int playerHealth ) { + // Get the survival probability against this creature and store it + survivalValues[ survivalRover ] = GetProbability( playerHealth ); + survivalRover = ( survivalRover + 1 ) % NUM_DAMAGES; +} + +//============================================================================= +// +// hhDDAProbability::GetProbability +// +//============================================================================= + +float hhDDAProbability::GetProbability( int value ) { + float z; + float x; + float probability = 0; + + if ( stdDeviation <= 0.0f ) { + stdDeviation = 1.0f; + } + + z = idMath::Fabs( value - mean ) / stdDeviation; + x = 1 + z * ( 0.049867347 + z * ( 0.0211410061 + z * ( 0.0032776263 ))); + probability = ( idMath::Exp( idMath::Log( x ) * -16 ) / 2 ); + if ( value >= mean ) { + probability = 1.0f - probability; + } + + return probability; +} + +//============================================================================= +// +// hhDDAProbability::CalculateProbabilityCurve +// +//============================================================================= + +void hhDDAProbability::CalculateProbabilityCurve() { + int i; + float diff; + float sumDiffSqr = 0; + + // Recompute mean and std deviation when new damage information is obtained + mean = 0; + for ( i = 0; i < NUM_DAMAGES; i++ ) { + mean += damages[i]; + } + mean /= NUM_DAMAGES; + + // calculate standard deviation + for ( i = 0; i < NUM_DAMAGES; i++ ) { + diff = mean - damages[i]; + sumDiffSqr += diff * diff; + } + + stdDeviation = idMath::Sqrt( sumDiffSqr / NUM_DAMAGES ); +} + +//============================================================================= +// +// hhDDAProbability::GetSurvivalMean +// +//============================================================================= + +float hhDDAProbability::GetSurvivalMean( void ) { + float sum = 0; + + for ( int i = 0; i < NUM_DAMAGES; i++ ) { + sum += survivalValues[i]; + } + + return sum / NUM_DAMAGES; +} + +//============================================================================= +// +// hhDDAProbability::AdjustDifficulty +// +//============================================================================= + +void hhDDAProbability::AdjustDifficulty( float diff ) { + individualDifficulty = idMath::ClampFloat( 0.0f, 1.0f, individualDifficulty + diff ); +} diff --git a/neo/prey/Prey/game_dda.h b/neo/prey/Prey/game_dda.h new file mode 100644 index 00000000..ae8e198f --- /dev/null +++ b/neo/prey/Prey/game_dda.h @@ -0,0 +1,128 @@ +/* +=============================================================================== +game_dda.h + This contains the functionality for a dynamic difficulty adjustment system, + as well as a statistic tracking system. +=============================================================================== +*/ +#ifndef __GAME_DDA_H__ +#define __GAME_DDA_H__ + +// DDA_Export Methods. +enum hhDDAExport { + DDA_ExportTEXT, + DDA_ExportCSV, +}; + +typedef struct ddaDeath_s { + int time; + idStr location; +} ddaDeath_t; + +const int DDA_INDEX_MAX = 16; + +//====================================================================================== +//====================================================================================== +// +// NEW DDA SYSTEM +// +//====================================================================================== +//====================================================================================== + +#define NUM_DAMAGES 8 + +class hhDDAProbability { +public: + hhDDAProbability(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void AddDamage( int damage ); + void AddSurvivalValue( int playerHealth ); + + float GetProbability( int value ); + float GetSurvivalMean( void ); + + void AdjustDifficulty( float diff ); + float GetIndividualDifficulty( void ) { return individualDifficulty; } + + bool IsUsed( void ) { return bUsed; } + +protected: + + void CalculateProbabilityCurve(); + + bool bUsed; // True if this probability instance is used (so the damage probability of later creatures doesn't influence earlier levels) + + int damages[NUM_DAMAGES]; + int damageRover; + + float survivalValues[NUM_DAMAGES]; + int survivalRover; + + float mean; + float stdDeviation; + + float individualDifficulty; +}; + +/*********************************************************************** + hhDDAManager. + Class that controls the DDA tracking and difficulty of the game. +***********************************************************************/ +class hhDDAManager { +public: + hhDDAManager(); + ~hhDDAManager(); +public: + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Public difficulty functions/accessors.. + float GetDifficulty(); //Called to get current difficulty rating (0.0 - 1.0) + void DifficultySummary(); + void ForceDifficulty( float newDifficulty ); // Force the difficulty to this value + + void RecalculateDifficulty( int updateFlags ); + + //Public DDA Notifications. (these are called to update the DDA system about activities happening in the game) + void DDA_Heartbeat( hhPlayer* player ); + + void DDA_AddDamage( int ddaIndex, int damage ); + void DDA_AddSurvivalHealth( int ddaIndex, int health ); + void DDA_AddDeath( hhPlayer *player, idEntity *attacker ); + + float DDA_GetProbability( int ddaIndex, int value ); + + //Public DDA Exporting functions. These are called to export DDA stats to either the console or a file. + void Export( const char* filename ); + + void PrintDDA( void ); + + //Public DDA Controls. These are used to clear/reset the DDA system (on map-switching). + void ClearTracking(); + +protected: + //Protected Internal functions for exporting purposes. + void ExportDDAData( idStr fileName, const char* header, const char *fileAddition, idList data ); + void ExportLINData( idStr FileName ); + + //Protected Interal Difficulty 'Ticker'. Difficulty system will re-evaluate whether to change diff here. + //o void DifficultyUpdate(); + + //Protected Internal Difficulty data. + bool bForcedDifficulty; // Used for testing, and Wicked mode + float difficulty; // Our current difficulty rating. + hhDDAProbability ddaProbability[DDA_INDEX_MAX]; // CJR: array of probabilites used in the DDA system + + idList locationNameData; + idList locationData; + idList healthData; + idList healthSpiritData; + idList ammoData; + idList miscData; + idList deathData; +}; + +#endif // __GAME_DDA_H__ \ No newline at end of file diff --git a/neo/prey/Prey/game_deathwraith.cpp b/neo/prey/Prey/game_deathwraith.cpp new file mode 100644 index 00000000..434c34b3 --- /dev/null +++ b/neo/prey/Prey/game_deathwraith.cpp @@ -0,0 +1,559 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +#define DEBUGGING_WRAITHS 0 + +//============================================================================= +// hhDeathWraith +//============================================================================= + +CLASS_DECLARATION( hhWraith, hhDeathWraith ) + EVENT( AI_FindEnemy, hhWraith::Event_FindEnemy ) +END_CLASS + + +//============================================================================= +// +// hhDeatWraith::Spawn +// +//============================================================================= + +void hhDeathWraith::Spawn() { + // Each wraith is assigned a random distance, rotation speed, and height around the enemy + idVec3 minCircleInfo = spawnArgs.GetVector( "min_circleInfo", "100 10 100" ); // dist, speed, height + idVec3 maxCircleInfo = spawnArgs.GetVector( "max_circleInfo", "100 10 100" ); // dist, speed, height + + circleDist = minCircleInfo[0] + gameLocal.random.RandomFloat() * ( maxCircleInfo[0] - minCircleInfo[0] ); + circleSpeed = minCircleInfo[1] + gameLocal.random.RandomFloat() * ( maxCircleInfo[1] - minCircleInfo[1] ); + circleHeight = minCircleInfo[2] + gameLocal.random.RandomFloat() * ( maxCircleInfo[2] - minCircleInfo[2] ); + + circleClockwise = (gameLocal.random.RandomFloat() > 0.5f ? true : false); + + // Compute the random attack time (in frames) + attackTimeMin = spawnArgs.GetFloat( "min_attackTime", "4" ) * 60; + attackTimeDelta = ( spawnArgs.GetFloat( "max_attackTime", "8" ) * 60 ) - attackTimeMin; + + if ( state == WS_FLY ) { + // The wraith is now circling the enemy - set the attack time + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + } + + // Determine the wraith type + healthWraith = (gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "healthTypeChance", "0.35" ) ? true : false); +} + +//============================================================================= +// +// hhDeathWraith::FlyUp +// +//============================================================================= +void hhDeathWraith::FlyUp() { + hhWraith::FlyUp(); + + if ( state == WS_FLY ) { + // The wraith is now circling the enemy - set the attack time + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + } +} + +//============================================================================= +// +// hhDeathWraith::FlyToEnemy +// +// Circle an enemy +//============================================================================= +void hhDeathWraith::FlyToEnemy() { + float distAdjust; + idVec3 origin; + float delta; + bool dir; + +#if DEBUGGING_WRAITHS +gameRenderWorld->DebugLine(colorWhite, GetOrigin(), GetOrigin()+idVec3(0,0,5), 5000); +#endif + + // Look for an enemy for the Wraith + // FIXME: This needs to be done often, because the player could spiritwalk after the Wraith has him as the enemy + // and we want the wraiths to try to target the player's proxy. + // We could either have this check via a heartbeat, or have some type of broadcast message when the player spiritwalks (?) + Event_FindEnemy( false ); + + // Ensure that the wraith is on the correct path, and if not, then set the wraith's velocity and angle to return to the path + origin = GetOrigin(); + idVec3 toEnemy = enemy->GetOrigin() - origin; + toEnemy.z = 0; + float distance = toEnemy.Normalize(); + + idVec3 tangent( -toEnemy.y, toEnemy.x, 0 ); // Quick tangent, swap x and y + + // If counter-clockwise, then reverse the tangent vector + if ( !circleClockwise ) { + tangent *= -1; + } + + // Determine if the radius is too large and gradually pull the Wraith in closer or if the wraith is too close, push it out + if ( distance > circleDist + circleSpeed ) { // Move closer + distAdjust = circleSpeed * 0.1f; + } else if ( distance < circleDist - circleSpeed ) { // Move farther away + distAdjust = -circleSpeed * 0.1f; + } else { // No adjustment + distAdjust = 0; + } + + // Decide if the wraith should change z-height + idVec3 zVelocity( 0, 0, 0 ); + + if ( origin.z < enemy->GetOrigin().z + circleHeight ) { + zVelocity.z = 2.0f; + } else if ( origin.z > enemy->GetOrigin().z + circleHeight + 30.0f ) { + zVelocity.z = -2.0f; + } + + // Move the wraith through the world + idVec3 velocity = tangent * circleSpeed + toEnemy * distAdjust + zVelocity; + + // Rotate towards velocity direction + dir = GetFacePosAngle( GetOrigin() + velocity, delta ); + + if ( delta > this->turn_threshold ) { // Wraith should turn + if ( dir ) { // Turn to the left + deltaViewAngles.yaw = this->turn_radius_max * (60.0f * USERCMD_ONE_OVER_HZ); + } else { + deltaViewAngles.yaw = -this->turn_radius_max * (60.0f * USERCMD_ONE_OVER_HZ); + } + } + + SetOrigin( GetOrigin() + (velocity * (60.0f * USERCMD_ONE_OVER_HZ)) ); + + // Decide if the wraith has waited long enough and then attack + if ( --countDownTimer <= 0 ) { + if ( health > 0 && enemy.IsValid() ) { // Only attack if the wraith has an enemy (which it should at all times) and the wraith hasn't been killed + EnterAttackState(); + } else { // Reset the attack time + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + } + } +} + +//============================================================================= +// +// hhDeathWraith::Damage +// +//============================================================================= +void hhDeathWraith::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Skip wraiths brighten when damaged logic + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +//============================================================================= +// +// hhDeathWraith::Killed +// +// When a deathwraith is killed, it immediately is removed +// The queue is reset and a new wraith will be spawned shortly after +//============================================================================= +void hhDeathWraith::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhFxInfo fxInfo; + + // Stop flyloop and chatter sounds, if any + StopSound( SND_CHANNEL_BODY ); + StopSound( SND_CHANNEL_VOICE ); + + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + SetShaderParm( SHADERPARM_DIVERSITY, 1.0f ); + + const char *deathEffect = spawnArgs.GetString("fx_deatheffect1"); + if (attacker && attacker->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast( attacker ); + if ( healthWraith ) { + deathEffect = spawnArgs.GetString("fx_deatheffect2"); + } + } + + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoAlongBone( deathEffect, spawnArgs.GetString( "joint_collision" ), &fxInfo ); + + physicsObj.SetContents(0); + fl.takedamage = false; + UnlinkCombat(); + + StartSound( "snd_death", SND_CHANNEL_VOICE ); + + NotifyDeath( inflictor, attacker ); + + PostEventSec( &EV_Remove, spawnArgs.GetFloat( "burnOutTime" ) ); +} + +//============================================================================= +// +// hhDeathWraith::NotifyDeath +// +// Handles notification of player on death +//============================================================================= +void hhDeathWraith::NotifyDeath( idEntity *inflictor, idEntity *attacker ) { + + if ( attacker && attacker->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( attacker ); + + SpawnEnergy(player); + + if ( player->IsDeathWalking() ) { + player->KilledDeathWraith(); + } + } +} + +//============================================================================= +// +// hhDeathWraith::SpawnEnergy +// +// Energy travels from wraith to deathWalkProxy +//============================================================================= +void hhDeathWraith::SpawnEnergy(hhPlayer *player) { + idEntity *destEntity = NULL; + const char *deathEnergyName = NULL; + idVec3 origin; + idMat3 axis; + idDict args; + + if ( healthWraith ) { + // Spawn health energy + deathEnergyName = spawnArgs.GetString("def_deathEnergyHealth", NULL); + } + else { + // Spawn spirit power energy + deathEnergyName = spawnArgs.GetString("def_deathEnergySpirit", NULL); + } + destEntity = player->GetDeathwalkEnergyDestination(); + + // Spawn the energy system that will deliver the spiritpower to the player + if (deathEnergyName && destEntity) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + args.Clear(); + args.Set("origin", center.ToString()); + idEntity *ent = gameLocal.SpawnObject(deathEnergyName, &args); + if (ent && ent->IsType(hhDeathWraithEnergy::Type)) { + hhDeathWraithEnergy *energy = static_cast(ent); + energy->SetPlayer(player); + energy->SetDestination( destEntity->GetOrigin() ); + } + } +} + +//============================================================================= +// +// hhDeathWraith::Think +// +//============================================================================= +void hhDeathWraith::Think( void ) { + + if ( enemy.IsValid() ) { + assert( enemy->IsType( hhPlayer::Type ) ); + hhPlayer *player = reinterpret_cast ( enemy.GetEntity() ); + + if( !player->IsDeathWalking() ) { + BecomeInactive( TH_THINK ); + PostEventMS( &EV_Remove, 0 ); + return; + } + else { + if ( healthWraith ) { + SetShaderParm(SHADERPARM_MODE, 1.0f); + } + else { + SetShaderParm(SHADERPARM_MODE, 0.0f); + } + } + } + + // Don't call hhWraith::Think(), since we don't want our health to drop over time + hhMonsterAI::Think(); + + // Since idAI is only capable of yawing, not piching, we post apply our pitch + if (state == WS_DEATH_CHARGE && enemy.IsValid()) { + idVec3 toEnemy = enemy->GetOrigin() - GetOrigin(); + toEnemy.Normalize(); + viewAxis = toEnemy.ToMat3(); + } + +#if DEBUGGING_WRAITHS + gameRenderWorld->DebugLine(colorRed, GetOrigin(), GetOrigin()+viewAxis[0]*20, 1000); + gameRenderWorld->DebugLine(colorGreen, GetOrigin(), GetOrigin()+viewAxis[1]*20, 1000); + gameRenderWorld->DebugLine(colorBlue, GetOrigin(), GetOrigin()+viewAxis[2]*20, 1000); +#endif +} + +//============================================================================= +// +// hhDeathWraith::FlyMove +// +//============================================================================= +void hhDeathWraith::FlyMove( void ) { + idMat3 axis; + idVec3 delta; + idVec3 toEnemy; + + // The state of the creature determines how it will move + switch ( state ) { + case WS_SPAWN: + FlyUp(); // Flying up right after spawn + break; + case WS_FLY: + FlyToEnemy(); + break; + case WS_FLEE: // Flying away + FlyAway(); + break; + case WS_DEATH_CHARGE: // Charging an enemy + +#if 1 + // Tranform animation movement by actual 'pitched' axis + if (enemy.IsValid()) { + toEnemy = enemy->GetOrigin() - GetOrigin(); + toEnemy.Normalize(); + axis = toEnemy.ToMat3(); + } else { // HUMANHEAD mdl: Enemy might not be valid if player is just leaving deathwalk + axis = viewAxis; + } +#else + axis = viewAxis; +#endif + + if (ChargeEnemy()) { + // Using animation delta to generate movement + physicsObj.UseFlyMove( false ); + physicsObj.UseVelocityMove( false ); + GetMoveDelta( axis, axis, delta ); + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( true ); + RunPhysics(); + } + return; + case WS_STILL: // Wraith is not moving at all (playing a specific anim, etc) + return; + } + + // run the physics for this frame + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( disableGravity ); + RunPhysics(); +} + + +//============================================================================= +// +// hhDeathWraith::EnterAttackState +// +//============================================================================= +void hhDeathWraith::EnterAttackState() { + state = WS_DEATH_CHARGE; + + // Set the wraith's rotation target towards the enemy + TurnTowardEnemy(); + + SetShaderParm( SHADERPARM_MISC, 1 ); // Charging + StartSound( "snd_charge", SND_CHANNEL_VOICE2 ); + + countDownTimer = PlayAnim( "alert", 2 ) / USERCMD_MSEC; +} + +//============================================================================= +// +// hhDeathWraith::ChargeEnemy +// +//============================================================================= +bool hhDeathWraith::ChargeEnemy( void ) { + + if ( !enemy.IsValid() ) { + ExitAttackState(); + return false; + } + + assert(enemy.IsValid() && enemy->IsType(hhPlayer::Type)); + + // Set the wraith's rotation target towards the enemy + TurnTowardEnemy(); + + // Attempt to attack the enemy + // If the wraith did not damage the enemy, then continue the attack flight + if ( !CheckEnemy() ) { + // Once the animation is finished (and the wraith wasn't killed or didn't hit the player), + // then transition to circle mode which will handle getting the wraith back to the path + if ( --countDownTimer <= 0 ) { + ExitAttackState(); + return false; + } + } + return true; +} + +//============================================================================= +// +// hhDeathWraith::ExitAttackState +// +//============================================================================= +void hhDeathWraith::ExitAttackState() { + state = WS_FLY; + PlayCycle( "fly", 2 ); + countDownTimer = attackTimeMin + gameLocal.random.RandomInt( attackTimeDelta ); + SetShaderParm( SHADERPARM_MISC, 0 ); // No longer charging + + GetPhysics()->SetLinearVelocity(vec3_origin); +} + +//============================================================================= +// +// hhDeathWraith::CheckEnemy +// +//============================================================================= +bool hhDeathWraith::CheckEnemy( void ) { + + // Don't try to damage if the wraith doesn't have a target, or if the wraith is dead or harvested + if ( health <= 0 || !enemy.IsValid() || fl.takedamage == false ) { + return false; + } + + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + idVec3 dir = enemy->GetPhysics()->GetAbsBounds().GetCenter() - center; + + if ( dir.LengthSqr() < spawnArgs.GetFloat( "minDamageDistSqr", "2500" ) ) { + HitEnemy(); + return true; + } + + return false; +} + +//============================================================================= +// +// hhDeathWraith::HitEnemy +// +//============================================================================= +void hhDeathWraith::HitEnemy( void ) { + hhFxInfo fxInfo; + + if (IsHidden()) { + return; + } + + // Activate the death trigger + idEntityPtr deathTrigger = gameLocal.FindEntity( spawnArgs.GetString( "triggerOnHit" ) ); + if ( deathTrigger.IsValid() ) { + if ( deathTrigger->RespondsTo( EV_Activate ) || deathTrigger->HasSignal( SIG_TRIGGER ) ) { + deathTrigger->Signal( SIG_TRIGGER ); + deathTrigger->ProcessEvent( &EV_Activate, this ); + } + } + + Hide(); + fl.takedamage = false; + + // Hit enemy effects -- for now, using the death effects + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_hitenemy", center, mat3_identity, &fxInfo ); + StartSound( "snd_attack", SND_CHANNEL_VOICE ); + + ApplyImpulseToEnemy(); + + if (enemy->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(enemy.GetEntity()); + + if ( healthWraith ) { + int playerHealth = player->GetHealth(); + int minHealth = player->spawnArgs.GetInt( "minResurrectHealth", "50" ); + + playerHealth -= spawnArgs.GetInt( "playerHealthDamage", "10" ); + if ( playerHealth < minHealth ) { + playerHealth = minHealth; + } + player->SetHealth( playerHealth ); + } else { + player->DeathWalkDamagedByWraith( this, spawnArgs.GetString( "def_damagetype" ) ); + } + } + + PostEventMS( &EV_Remove, 2000 ); +} + +//============================================================================= +// +// hhDeathWraith::ApplyImpulseToEnemy +// +//============================================================================= +void hhDeathWraith::ApplyImpulseToEnemy() { + if( enemy.IsValid() ) { + idMat3 axis = GetAxis(); + idVec3 impulseDir = axis[0] - axis[2]; + impulseDir.Normalize(); + enemy->ApplyImpulse( this, 0, enemy->GetOrigin(), impulseDir * spawnArgs.GetFloat("impulseMagnitude") * enemy->GetPhysics()->GetMass() ); + } +} + +//============================================================================= +// +// hhDeathWraith::EnemyDead +// +//============================================================================= +void hhDeathWraith::EnemyDead() { + return; // Death wraiths expect their enemies to be dead. +} + +//============================================================================= +// +// hhDeathWraith::PlayCycle +// +//============================================================================= +void hhDeathWraith::PlayCycle( const char *name, int blendFrame ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrame) ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim(name), gameLocal.GetTime(), FRAME2MS(blendFrame) ); +} + +//============================================================================= +// +// hhDeathWraith::PlayAnim +// +//============================================================================= +int hhDeathWraith::PlayAnim( const char *name, int blendFrame ) { + int fadeTime = FRAME2MS( blendFrame ); + int animLength = 0; + + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrame) ); + + int anim = GetAnimator()->GetAnim( name ); + if ( anim ) { + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.GetTime(), fadeTime ); + animLength = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + animLength = (animLength > fadeTime) ? animLength - fadeTime : animLength; + } + + return animLength; +} + +//============================================================================= +// +// hhDeathWraith::Event_FindEnemy +// +//============================================================================= +void hhDeathWraith::Event_FindEnemy( int useFOV ) { + // These are meant for single player deathwalk only + idPlayer *player = gameLocal.GetLocalPlayer(); + SetEnemy( player ); + idThread::ReturnEntity( player ); +} + +//============================================================================= +// +// hhDeathWraith::TeleportIn +// +//============================================================================= + +void hhDeathWraith::TeleportIn( idEntity *activator ) { + Show(); + PostEventMS( &EV_Activate, 0, activator ); +} diff --git a/neo/prey/Prey/game_deathwraith.h b/neo/prey/Prey/game_deathwraith.h new file mode 100644 index 00000000..447d296a --- /dev/null +++ b/neo/prey/Prey/game_deathwraith.h @@ -0,0 +1,48 @@ +#ifndef __GAME_DEATHWRAITH_H__ +#define __GAME_DEATHWRAITH_H__ + +class hhDeathWraith : public hhWraith { + public: + CLASS_PROTOTYPE( hhDeathWraith ); + + void Spawn(); + virtual void FlyToEnemy(); + virtual void SetEnemy( idActor *newEnemy ) { enemy = newEnemy; } + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Think( void ); + + protected: + virtual int PlayAnim( const char *name, int blendFrame ); + virtual void PlayCycle( const char *name, int blendFrame ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void NotifyDeath( idEntity *inflictor, idEntity *attacker ); + void SpawnEnergy(hhPlayer *player); + void EnterAttackState(); + void ExitAttackState(); + virtual bool ChargeEnemy(); + bool CheckEnemy(); + void HitEnemy(); + void ApplyImpulseToEnemy(); + virtual void FlyUp(); + virtual void FlyMove( void ); + virtual void EnemyDead(); + void Event_FindEnemy( int useFOV ); + void TeleportIn( idEntity *activator ); + + protected: + // Variables for controlling the movement of the wraith around the anchor point + // These variables are unique for each wraith for variety + float circleDist; // Distance to stay from the anchor point + float circleSpeed; // Orbit speed + float circleHeight; // Height above the point + bool circleClockwise; // if true, orbit clockwise + + int attackTimeMin; // Range for the attack time + int attackTimeDelta; // Range for the attack time + + idMat3 chargeAxis; + + bool healthWraith; // true if a health type +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_debrisspawner.cpp b/neo/prey/Prey/game_debrisspawner.cpp new file mode 100644 index 00000000..7e15a18f --- /dev/null +++ b/neo/prey/Prey/game_debrisspawner.cpp @@ -0,0 +1,445 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_RemoveAll( "removeAll", NULL ); + +CLASS_DECLARATION( idEntity, hhDebrisSpawner ) + EVENT( EV_Activate, hhDebrisSpawner::Activate ) +END_CLASS + +/* +============ +hhDebrisSpawner::hhDebrisSpawner() +============ +*/ +hhDebrisSpawner::hhDebrisSpawner() { +} + + +/* +============ +hhDebrisSpawner::~hhDebrisSpawner() +============ +*/ +hhDebrisSpawner::~hhDebrisSpawner() { +} + + +/* +============ +hhDebrisSpawner::Spawn() + Spawns the entity, and all other entities associated with the mass +============ +*/ +void hhDebrisSpawner::Spawn() { + + // Set the defaults + activated = false; + sourceEntity = NULL; + hasBounds = false; + + // Get the passed in + spawnArgs.GetVector( "origin", "0 0 0", origin ); + spawnArgs.GetVector( "orientation", "1 0 0", orientation ); + spawnArgs.GetVector( "velocity", "0 0 0", velocity ); + spawnArgs.GetVector( "power", "50 50 50", power ); + spawnArgs.GetFloat( "duration", "0", duration ); + spawnArgs.GetBool( "multi_trigger", "0", multiActivate ); + spawnArgs.GetBool( "spawnUsingEntity", "0", useEntity ); + spawnArgs.GetBool( "fillBounds", "1", fillBounds ); + spawnArgs.GetBool( "testBounds", "1", testBounds ); + spawnArgs.GetBool( "nonsolid", "0", nonSolid ); + spawnArgs.GetBool( "useAFBounds", "0", useAFBounds ); //rww + + // Hide the model + GetPhysics()->SetContents( 0 ); + Hide(); + bounds = GetPhysics()->GetClipModel()->GetBounds(); + GetPhysics()->DisableClip(); + + // Do stuff + power *= 20; + + // spawnWhenActivated means that + if ( !useEntity && !spawnArgs.GetInt( "trigger", "0" ) ) { + Activate( NULL ); + } +} + +void hhDebrisSpawner::Save(idSaveGame *savefile) const { + savefile->WriteVec3(origin); + savefile->WriteVec3(orientation); + savefile->WriteVec3(velocity); + savefile->WriteVec3(power); + savefile->WriteBool(activated); + savefile->WriteBool(multiActivate); + savefile->WriteBool(hasBounds); + savefile->WriteBounds(bounds); + savefile->WriteFloat(duration); + savefile->WriteBool(fillBounds); + savefile->WriteBool(testBounds); + savefile->WriteObject(sourceEntity); +} + +void hhDebrisSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3(origin); + savefile->ReadVec3(orientation); + savefile->ReadVec3(velocity); + savefile->ReadVec3(power); + savefile->ReadBool(activated); + savefile->ReadBool(multiActivate); + savefile->ReadBool(hasBounds); + savefile->ReadBounds(bounds); + savefile->ReadFloat(duration); + savefile->ReadBool(fillBounds); + savefile->ReadBool(testBounds); + savefile->ReadObject( reinterpret_cast(sourceEntity) ); + + spawnArgs.GetBool( "spawnUsingEntity", "0", useEntity ); + spawnArgs.GetBool( "nonsolid", "0", nonSolid ); + spawnArgs.GetBool( "useAFBounds", "0", useAFBounds ); +} + + +/* +================ +hhDebrisSpawner::Activate + Do the actual spawning of debris, FX, etc.. +================ +*/ +void hhDebrisSpawner::Activate( idEntity *aSourceEntity ) { + + if ( useEntity ) { + sourceEntity = aSourceEntity; + } + else { // If we don't have an entity, use our bounds instead + hasBounds = true; + } + + if ( !activated || multiActivate ) { + SpawnDebris(); + + SpawnFX(); + SpawnDecals(); + + // If we have a fixed duration, remove ourselves after they are removed + if ( ( duration > 0.0f ) && !multiActivate ) { + PostEventSec( &EV_Remove, duration + 1.0f ); + } + + activated = true; + } +} + + +/* +============ +hhDebrisSpawner::SpawnDebris +============ +*/ +void hhDebrisSpawner::SpawnDebris() { + const idKeyValue * kv = NULL; + idEntity * debris; + idDict args; + idStr debrisEntity; + idList debrisEntities; + int numDebris; + // For entity sources + idBounds sourceBounds; + idVec3 sourceBoundCenter; + idVec3 debrisOrigin; + idBounds searchBounds; + idVec3 debrisVelocity; + bool fit; + idBounds afBounds; //rww + bool gotAFBounds = false; //rww + idVec3 defOrigin = vec3_zero; + idAngles defAngles = ang_zero; + + // Optimization: if player is far away, don't spawn the chunks + if (!gameLocal.isMultiplayer) { + float maxDistance = spawnArgs.GetFloat("max_debris_distance"); + if (maxDistance > 0.0f) { + float distSquared = (gameLocal.GetLocalPlayer()->GetOrigin() - origin).LengthSqr(); + if (distSquared > maxDistance*maxDistance) { + return; + } + } + } + args.SetInt( "nodrop", 1 ); + args.SetVector( "origin", origin ); + args.SetFloat( "duration", duration ); + + // Pass along variabes requested + hhUtils::PassArgs( spawnArgs, args, "pass_" ); + + if (useAFBounds && sourceEntity && sourceEntity->IsType(idActor::Type)) { //rww - try for AF bounds + idActor *actor = static_cast(sourceEntity); + if (actor->IsActiveAF()) { //they are ragdolling, so we have a chance. + idPhysics_AF *afPhys = actor->GetAFPhysics(); + + if (afPhys) { //got the af physics object, now loop through the bodies and collect an appropriate bounds. + afBounds.Zero(); + + for (int bodyCount = 0; bodyCount < afPhys->GetNumBodies(); bodyCount++) { + idAFBody *b = afPhys->GetBody(bodyCount); + if (b) { + idClipModel *bodyClip = b->GetClipModel(); + if (bodyClip) { //now add the bounds of the clipModel into afBounds + idBounds bodyBounds = bodyClip->GetBounds(); + idVec3 point = (b->GetWorldOrigin()-origin); + + bodyBounds.AddPoint(point); + afBounds.AddBounds(bodyBounds); + gotAFBounds = true; + } + } + } + } + } + } + + // Find all Debris + while ( GetNextDebrisData( debrisEntities, numDebris, kv, defOrigin, defAngles ) ) { + int numEntities = debrisEntities.Num(); + for ( int count = 0; count < numDebris; ++count ) { + // Select a debris to use from the list + debrisEntity = debrisEntities[ gameLocal.random.RandomInt( numEntities ) ]; + + // If we have a bounding box to fill, use that + if ( fillBounds && ( sourceEntity || hasBounds ) ) { + if (gotAFBounds) { //rww + sourceBounds = afBounds; + //gameRenderWorld->DebugBounds(colorGreen, afBounds, origin, 1000); + } + else if ( sourceEntity ) { + sourceBounds = sourceEntity->GetPhysics()->GetBounds(); + } + else { + sourceBounds = bounds; + } + + fit = false; + for ( int i = 0; i < 4; i++ ) { + if ( i == 0 && defOrigin != vec3_zero ) { + //first try bone origin without random offset + debrisOrigin = defOrigin; + } else { + debrisOrigin = origin + hhUtils::RandomPointInBounds( sourceBounds ); + } + + if ( !testBounds || hhUtils::EntityDefWillFit( debrisEntity, debrisOrigin, defAngles.ToMat3(), CONTENTS_SOLID, NULL ) ) { + fit = true; + break; // Found a spot for the gib + } + } + + if ( !fit ) { // Gib didn't fit after 4 attempts, so don't spawn the gib + //common->Warning( "SpawnDebris: gib didn't fit when spawned (%s)\n", debrisEntity.c_str() ); + defAngles = ang_zero; + continue; + } + + args.SetVector( "origin", debrisOrigin ); + if ( sourceEntity ) { + idMat3 sourceAxis = idAngles( 0, sourceEntity->GetAxis().ToAngles().yaw, 0 ).ToMat3(); + args.SetMatrix( "rotation", defAngles.ToMat3() * sourceAxis ); + } else { + args.SetMatrix( "rotation", defAngles.ToMat3() ); + } + } + + + if ( duration ) { + args.SetFloat( "removeTime", duration + duration * gameLocal.random.CRandomFloat() ); + } + + // Spawn the object + debris = gameLocal.SpawnObject( debrisEntity, &args ); + HH_ASSERT(debris != NULL); // JRM - Nick, I added this because of a crash I got. Hopefully this will catch it sooner + + // mdl: Added this check to make sure no AFs go through the debris spawner + if ( debris->IsType( idAFEntity_Base::Type ) ) { + gameLocal.Warning( "hhDebrisSpawner spawned an articulated entity: '%s'.", debrisEntity.c_str() ); + } + + if ( nonSolid ) { + + idVec3 origin; + if ( debris->GetFloorPos( 64.0f, origin ) ) { + debris->SetOrigin( origin ); // Start on floor, since we're not going to be moving at all + debris->RunPhysics(); // Make sure any associated effects get updated before we turn collision off + } + + // Turn off collision + debris->GetPhysics()->SetContents( 0 ); + debris->GetPhysics()->SetClipMask( 0 ); + debris->GetPhysics()->UnlinkClip(); + debris->GetPhysics()->PutToRest(); + + } else { + // Add in random velocity + idVec3 randVel( gameLocal.random.RandomInt( power.x ) - power.x / 2, + gameLocal.random.RandomInt( power.y ) - power.y / 2, + gameLocal.random.RandomInt( power.z ) - power.z / 2 ); + + // Set linear velocity + debrisVelocity.x = velocity.x * power.x * 0.25; + debrisVelocity.y = velocity.y * power.y * 0.25; + debrisVelocity.z = 0.0f; + debris->GetPhysics()->SetLinearVelocity( debrisVelocity + randVel ); + + // Add random angular velocity + idVec3 aVel; + aVel.x = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat(); + aVel.y = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat(); + aVel.z = spawnArgs.GetFloat( "ang_vel", "90.0" ) * gameLocal.random.CRandomFloat(); + + if ( defAngles == ang_zero ) { + debris->GetPhysics()->SetAxis( idVec3( 1, 0, 0 ).ToMat3() ); + } + debris->GetPhysics()->SetAngularVelocity( aVel ); + } + } + defAngles = ang_zero; + + // Zero out the list + debrisEntities.Clear(); + } + + return; +} + + +/* +============ +hhDebrisSpawner::SpawnFX +============ +*/ +void hhDebrisSpawner::SpawnFX() { + hhFxInfo fxInfo; + + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx", origin, orientation.ToMat3(), &fxInfo ); +} + + +//============================================================================= +// +// hhDebrisSpawner::SpawnDecals +// +//============================================================================= + +void hhDebrisSpawner::SpawnDecals( void ) { + const int DIR_COUNT = 5; + idVec3 dir[DIR_COUNT] = { + idVec3(1.0f, 0.0f, 0.0f), + idVec3(-1.0f, 0.0f, 0.0f), + idVec3(0.0f, 0.0f, -1.0f), + idVec3(0.0f, 1.0f, 0.0f), + idVec3(0.0f, -1.0f, 0.0f) + }; + + // do blood splats + float size = spawnArgs.GetFloat( "decal_size", "96" ); + + const idKeyValue* kv = spawnArgs.MatchPrefix( "mtr_decal" ); + while( kv ) { + if( kv->GetValue().Length() ) { + gameLocal.ProjectDecal( origin, dir[ gameLocal.random.RandomInt( DIR_COUNT ) ], 64.0f, true, size, kv->GetValue().c_str() ); + } + kv = spawnArgs.MatchPrefix( "mtr_decal", kv ); + } +} + +/* +============ +hhDebrisSpawner::GetNextDebrisData +============ +*/ +bool hhDebrisSpawner::GetNextDebrisData( idList &entityDefs, int &count, const idKeyValue * &kv, idVec3 &origin, idAngles &angle ) { + static char debrisDef[] = "def_debris"; + static char debrisKey[] = "debris"; + idStr indexStr; + idStr entityDefBase; + int numDebris, minDebris, maxDebris; + origin = vec3_zero; + + // Loop through looking for the next valid debris key + for ( kv = spawnArgs.MatchPrefix( debrisDef, kv ); + kv && kv->GetValue().Length(); + kv = spawnArgs.MatchPrefix( debrisDef, kv ) ) { + indexStr = kv->GetKey(); + indexStr.Strip( debrisDef ); + + // Is a valid debris base And it isn't a variation. (ie, contains a .X postfix) + if ( idStr::IsNumeric( indexStr ) && indexStr.Find( '.' ) < 0 ) { + + // Get Number of Debris + numDebris = -1; + + if ( sourceEntity && sourceEntity->IsType( idAI::Type ) ) { + idVec3 bonePos; + idMat3 boneAxis; + idStr bone = spawnArgs.GetString( va( "%s%s%s", debrisKey, "_bone", ( const char * ) indexStr ) ); + if( !bone.IsEmpty() ) { + static_cast(sourceEntity)->GetJointWorldTransform( bone.c_str(), bonePos, boneAxis ); + origin = bonePos; + angle = spawnArgs.GetAngles( va( "%s%s%s", debrisKey, "_angle", ( const char * ) indexStr ) ); + } + } + if ( !spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_num", + ( const char * ) indexStr ), + "-1", numDebris ) || numDebris < 0 ) { + // No num found, look for min and max + if ( spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_min", + ( const char * ) indexStr ), + "-1", minDebris ) && minDebris >= 0 && + spawnArgs.GetInt( va( "%s%s%s", debrisKey, "_max", + ( const char * ) indexStr ), + "-1", maxDebris ) && maxDebris >= 0 ) { + numDebris = + gameLocal.random.RandomInt( ( maxDebris - minDebris ) ) + minDebris; + } //. No min/max found + + } //. No num found + + // No valid num found + if ( numDebris < 0 ) { + gameLocal.Warning( "ERROR: No debris num could be be found for %s%s", + ( const char * ) debrisDef, + ( const char * ) indexStr ); + } + else { // Valid num found + const char * entityDefPrefix; + const idKeyValue * otherDefKey = NULL; + + entityDefBase = kv->GetValue(); + count = numDebris; + + entityDefs.Append( entityDefBase ); + + // Find the other defs that may be there. using .X scheme + entityDefPrefix = va( "%s.", (const char *) kv->GetKey() ); + + // gameLocal.Printf( "Looking for %s\n", entityDefPrefix ); + + for ( otherDefKey = spawnArgs.MatchPrefix( entityDefPrefix, otherDefKey ); + otherDefKey && otherDefKey->GetValue().Length(); + otherDefKey = spawnArgs.MatchPrefix( entityDefPrefix, otherDefKey ) ) { + // gameLocal.Printf( "Would have added %s\n", (const char *) otherDefKey->GetValue() ); + entityDefs.Append( otherDefKey->GetValue() ); + } + + return( true ); + } + + } //. Valid debris base + } //. debris loop + + + return( false ); +} + diff --git a/neo/prey/Prey/game_debrisspawner.h b/neo/prey/Prey/game_debrisspawner.h new file mode 100644 index 00000000..8ba352d5 --- /dev/null +++ b/neo/prey/Prey/game_debrisspawner.h @@ -0,0 +1,47 @@ +#ifndef __PREY_GAME_DEBRISMASS_H__ +#define __PREY_GAME_DEBRISMASS_H__ + +class hhDebrisSpawner : public idEntity { + CLASS_PROTOTYPE( hhDebrisSpawner ); + + public: + hhDebrisSpawner(); + virtual ~hhDebrisSpawner(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Activate( idEntity *sourceEntity ); + + ID_INLINE float GetDuration() const { return duration; } + + protected: + void Event_RemoveAll(); + + void SpawnDebris(); + void SpawnFX(); + void SpawnDecals( void ); + + bool GetNextDebrisData( idList &entityDefs, int &count, + const idKeyValue * &kv, idVec3 &origin, idAngles &angle ); +protected: + idVec3 origin; + idVec3 orientation; + idVec3 velocity; + idVec3 power; + bool activated; + bool multiActivate; + bool useEntity; // Use an entity to spawn in + bool useAFBounds; //rww - use collective AF bodies to produce an appropriate bounds for spawning. + bool hasBounds; + idBounds bounds; + // Vars for removing everything after a fixed period + float duration; + + bool fillBounds; // Try to fill in the bounds with the gibs. + bool testBounds; // test to see if debris fits in bounds + bool nonSolid; + + idEntity * sourceEntity; +}; + +#endif __PREY_GAME_DEBRISMASS_H__ diff --git a/neo/prey/Prey/game_dock.cpp b/neo/prey/Prey/game_dock.cpp new file mode 100644 index 00000000..83f46378 --- /dev/null +++ b/neo/prey/Prey/game_dock.cpp @@ -0,0 +1,70 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhDock +// +//----------------------------------------------------------------------- +const idEventDef EV_DockLock("lockVehicleInDock", NULL); +const idEventDef EV_DockUnlock("unlockDock", NULL); + +ABSTRACT_DECLARATION(idEntity, hhDock) + EVENT( EV_DockLock, hhDock::Event_Lock) + EVENT( EV_DockUnlock, hhDock::Event_Unlock) + EVENT( EV_Activate, hhDock::Event_Activate) + EVENT( EV_PostSpawn, hhDock::Event_PostSpawn ) +END_CLASS + +void hhDock::Spawn() { + if ( !gameLocal.isClient ) { + PostEventMS(&EV_PostSpawn, 0); + } +} + +void hhDock::Event_PostSpawn() { + dockingZone = SpawnDockingZone(); +} + +void hhDock::Save(idSaveGame *savefile) const { + dockingZone.Save(savefile); +} + +void hhDock::Restore( idRestoreGame *savefile ) { + dockingZone.Restore(savefile); +} + +hhDockingZone *hhDock::SpawnDockingZone() { + hhDockingZone *zone; + idBounds localBounds; + + // Define our bounds + localBounds[0] = spawnArgs.GetVector("dockingzonemins"); + localBounds[1] = spawnArgs.GetVector("dockingzonemaxs"); + + // create a docking zone with this size + idDict args; + args.SetVector( "origin", GetOrigin() ); + args.SetVector( "mins", localBounds[0] ); + args.SetVector( "maxs", localBounds[1] ); + args.SetMatrix( "rotation", GetAxis() ); + zone = (hhDockingZone *)gameLocal.SpawnEntityType(hhDockingZone::Type, &args); + assert(zone); + zone->Bind(this, true); + zone->RegisterDock(this); + return zone; +} + +void hhDock::Event_Lock() { + Lock(); +} + +void hhDock::Event_Unlock() { + Unlock(); +} + +void hhDock::Event_Activate( idEntity *activator ) { + Unlock(); +} \ No newline at end of file diff --git a/neo/prey/Prey/game_dock.h b/neo/prey/Prey/game_dock.h new file mode 100644 index 00000000..005fdff3 --- /dev/null +++ b/neo/prey/Prey/game_dock.h @@ -0,0 +1,49 @@ +#ifndef __GAME_DOCK_H__ +#define __GAME_DOCK_H__ + +extern const idEventDef EV_DockLock; +extern const idEventDef EV_DockUnlock; + +class hhShuttle; +class hhDockingZone; + +class hhDock : public idEntity { +public: + ABSTRACT_PROTOTYPE( hhDock ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent) = 0; + virtual void EntityEntered( idEntity *ent ) = 0; + virtual void EntityEncroaching( idEntity *ent ) = 0; + virtual void EntityLeaving( idEntity *ent ) = 0; + virtual void ShuttleExit( hhShuttle *shuttle ) = 0; + virtual bool IsLocked() = 0; + virtual bool CanExitLocked() = 0; + virtual bool AllowsBoost() = 0; + virtual bool AllowsFiring() = 0; + virtual bool AllowsExit() = 0; + virtual bool IsTeleportDest() = 0; + virtual void UpdateAxis( const idMat3 &newAxis ) = 0; + + virtual bool Recharges() const { return false; } + +protected: + //rww - needs a delay for mp + virtual void Event_PostSpawn(); + + hhDockingZone* SpawnDockingZone(); + virtual void Lock()=0; + virtual void Unlock()=0; + + void Event_Lock(); + void Event_Unlock(); + void Event_Activate(idEntity *activator); + +protected: + idEntityPtr dockingZone; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_dockedgun.cpp b/neo/prey/Prey/game_dockedgun.cpp new file mode 100644 index 00000000..4dab2c55 --- /dev/null +++ b/neo/prey/Prey/game_dockedgun.cpp @@ -0,0 +1,16 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhDockedGun +// +//========================================================================== + +CLASS_DECLARATION(hhShuttle, hhDockedGun) +END_CLASS + +void hhDockedGun::Spawn() { +} diff --git a/neo/prey/Prey/game_dockedgun.h b/neo/prey/Prey/game_dockedgun.h new file mode 100644 index 00000000..cc97b2d0 --- /dev/null +++ b/neo/prey/Prey/game_dockedgun.h @@ -0,0 +1,19 @@ +#ifndef __GAME_DOCKEDGUN_H__ +#define __GAME_DOCKEDGUN_H__ + +//========================================================================== +// +// hhDockedGun +// +//========================================================================== + +class hhDockedGun : public hhShuttle { + CLASS_PROTOTYPE( hhDockedGun ); + +public: + void Spawn(); + +protected: +}; + +#endif diff --git a/neo/prey/Prey/game_door.cpp b/neo/prey/Prey/game_door.cpp new file mode 100644 index 00000000..b3c9977e --- /dev/null +++ b/neo/prey/Prey/game_door.cpp @@ -0,0 +1,586 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idDoor, hhDoor ) + EVENT( EV_Mover_ReturnToPos1, hhDoor::Event_ReturnToPos1 ) + EVENT( EV_ReachedPos, hhDoor::Event_Reached_BinaryMover ) + EVENT( EV_SetBuddiesShaderParm, hhDoor::Event_SetBuddiesShaderParm ) + EVENT( EV_Touch, hhDoor::Event_Touch ) +END_CLASS + +//-------------------------------- +// hhDoor::Spawn +//-------------------------------- +void hhDoor::Spawn() { + airlockMaster = NULL; + + airlockTeam.SetOwner( this ); + airlockTeamName = spawnArgs.GetString( "airlockTeam" ); + spawnArgs.GetFloat( "airlockwait", "3", airLockSndWait ); + airLockSndWait *= 1000.0f; // Convert from seconds to ms + + idEntity* master = DetermineTeamMaster( GetAirLockTeamName() ); + if( master ) { + airlockMaster = static_cast( master ); + if( airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + } + + if( spawnArgs.GetBool("start_open") ) { + VerifyAirlockTeamStatus(); + } + + if( moveMaster != this ) { + CopyTeamInfoToMoveMaster( static_cast(moveMaster) ); + } + + // We only open when dead if we have health to start with + openWhenDead = health > 0; + forcedOpen = false; + nextAirLockSnd = 0; + + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); +} + +void hhDoor::Save(idSaveGame *savefile) const { + savefile->WriteString(airlockTeamName); + savefile->WriteObject(airlockMaster); + savefile->WriteBool(openWhenDead); + savefile->WriteBool(forcedOpen); + savefile->WriteFloat(airLockSndWait); +} + +void hhDoor::Restore( idRestoreGame *savefile ) { + savefile->ReadString(airlockTeamName); + savefile->ReadObject( reinterpret_cast(airlockMaster) ); + savefile->ReadBool(openWhenDead); + savefile->ReadBool(forcedOpen); + savefile->ReadFloat(airLockSndWait); + + airlockTeam.SetOwner( this ); + if( airlockMaster && airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + + nextAirLockSnd = 0; + + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); +} + +//-------------------------------- +// hhDoor::~hhDoor +//-------------------------------- +hhDoor::~hhDoor() { + airlockTeam.Remove(); +} + +//-------------------------------- +// hhDoor::Killed +//-------------------------------- +void hhDoor::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + // Only can really be killed if we can be damaged + if ( openWhenDead ) { + Open(); + + fl.takedamage = false; + forcedOpen = true; + } +} // Killed( idEntity *, idEntity *, int, const idVec3 &, int ) + +//-------------------------------- +// hhDoor::Use_BinaryMover +//-------------------------------- +void hhDoor::Use_BinaryMover( idEntity *activator ) { + if ( airlockMaster && activator && !activator->IsType( idPlayer::Type ) ) { + return; // Don't allow anyone but the player to affect airlock doors + } + + // only the master should be used + if ( moveMaster != this ) { + moveMaster->Use_BinaryMover( activator ); + return; + } + + if ( !enabled ) { + return; + } + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) { + GotoPosition2(); + } + else if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 ) { + GotoPosition1(); + } +} + +//-------------------------------- +// hhDoor::GotoPosition1 +// +// CloseDoor +//-------------------------------- +void hhDoor::GotoPosition1() { + GotoPosition1( spawnArgs.GetBool("toggle") ? 0.0f : wait ); +} + +//-------------------------------- +// hhDoor::GotoPosition1 +// +// CloseDoor +//-------------------------------- +void hhDoor::GotoPosition1( float wait ) { + idMover_Binary* slave = NULL; + int partial = 0; + + //HUMANHEAD: aob - airlock stuff + if( !CanClose() ) { + return; + } + //HUMNAHEAD END + + if ( moverState == MOVER_POS2 ) { + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + CancelReturnToPos1(); + + if( wait > 0 ) { + //HUMANHEAD: aob + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + //HUMANHEAD END + } else { + ProcessEvent( &EV_Mover_ReturnToPos1 ); + } + } + + else + + // 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(); + MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); + } +} + +//-------------------------------- +// hhDoor::GotoPosition2 +// +// OpenDoor +//-------------------------------- +void hhDoor::GotoPosition2() { + GotoPosition2( 0.0f ); +} + +//-------------------------------- +// hhDoor::GotoPosition2 +// +// OpenDoor +//-------------------------------- +void hhDoor::GotoPosition2( float wait ) { + int partial = 0; + hhDoor *airlockMaster = GetAirLockMaster(); + + //HUMANHEAD: aob - airlock stuff + if( !CanOpen() && airlockMaster ) { + ForceAirLockTeamClosed(); + if( gameLocal.time > nextAirLockSnd ) { + int length; + StartSound( "snd_airlock", SND_CHANNEL_BODY, 0, false, &length ); + nextAirLockSnd = gameLocal.time + length + airLockSndWait; + } + return; + } + //HUMNAHEAD END + + if ( moverState == MOVER_POS1 ) { + ActivatePrefixed("triggerStartOpen", GetActivator()); + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + + MatchActivateTeam( MOVER_1TO2, gameLocal.time ); + + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + } + else if ( moverState == MOVER_2TO1 ) { // only partway up before reversing + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); + } + + if( airlockMaster ) { + // Mark the other team members as locked + for( hhDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as locked + node->SetBuddiesShaderParm( SHADERPARM_MISC, 1.0f ); + } + + // This prevents the our shader from being set as locked from the player moving too quickly between airlock doors + SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } +} + +//-------------------------------- +// hhDoor::Open +//-------------------------------- +void hhDoor::Open( void ) { + GotoPosition2( wait ); +} + +//-------------------------------- +// hhDoor::Close +//-------------------------------- +void hhDoor::Close( void ) { + // HUMANHEAD nla + if ( ForcedOpen() ) { return; } + // HUMANHEAD + + GotoPosition1( 0.0f ); +} + +//-------------------------------- +// hhDoor::CanOpen +//-------------------------------- +bool hhDoor::CanOpen() const { + if( !GetAirLockMaster() ) { + return true; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( OnMyMoveTeam(node) ) { + continue; + } + + if( node->IsOpen() ) { + return false; + } + } + + return true; +} + +//-------------------------------- +// hhDoor::CanClose +//-------------------------------- +bool hhDoor::CanClose() const { + return true; +} + +//-------------------------------- +// hhDoor::OnMyMoveTeam +//-------------------------------- +bool hhDoor::OnMyMoveTeam( hhDoor* doorPiece ) const { + for( const idMover_Binary* localDoorPiece = this; localDoorPiece != NULL; localDoorPiece = localDoorPiece->GetActivateChain() ) { + if( localDoorPiece == doorPiece ) { + return true; + } + } + + return false; +} + +//-------------------------------- +// hhDoor::CopyTeamInfoToMoveMaster +//-------------------------------- +void hhDoor::CopyTeamInfoToMoveMaster( hhDoor* master ) { + if( !master ) { + return; + } + + for( int ix = buddies.Num() - 1; ix >= 0; --ix ) { + master->buddies.AddUnique( buddies[ix] ); + } + + for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) { + master->buddyNames.AddUnique( buddyNames[ix] ); + } +} + +//-------------------------------- +// hhDoor::DetermineTeamMaster +//-------------------------------- +idEntity* hhDoor::DetermineTeamMaster( const char* teamName ) { + idEntity* ent = NULL; + + if ( teamName && teamName[0] ) { + // 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(hhModelDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + if (ent->IsType(hhDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + } + } + + return NULL; +} + +//-------------------------------- +// hhDoor::JoinAirLockTeam +//-------------------------------- +void hhDoor::JoinAirLockTeam( hhDoor *master ) { + assert( master ); + + airlockTeam.AddToEnd( master->airlockTeam ); +} + +//-------------------------------- +// hhDoor::VerifyAirlockTeamStatus +//-------------------------------- +void hhDoor::VerifyAirlockTeamStatus() { + if( !GetAirLockMaster() ) { + return; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( OnMyMoveTeam(node) ) { + continue; + } + + if( !node->IsClosed() ) { + gameLocal.Warning( "Airlock team '%s' has more than one member starting open", GetAirLockTeamName() ); + } + } +} + +//-------------------------------- +// hhDoor::IsClosed +//-------------------------------- +bool hhDoor::IsClosed() const { + return ( moverState == MOVER_POS1 ); +} + +//-------------------------------- +// hhDoor::ForceAirLockTeamClosed +//-------------------------------- +void hhDoor::ForceAirLockTeamClosed() { + hhDoor* localMoveMaster = NULL; + + if( !GetAirLockMaster() ) { + return; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( !node->moveMaster->IsType(hhDoor::Type) ) { + continue; + } + + localMoveMaster = static_cast( node->moveMaster ); + //if( node != localMoveMaster ) { + // continue; + //} + + localMoveMaster->CancelReturnToPos1(); + if( !localMoveMaster->IsClosed() ) { + localMoveMaster->Close(); + } + } +} + +//-------------------------------- +// hhDoor::ForceAirLockTeamOpen +//-------------------------------- +void hhDoor::ForceAirLockTeamOpen() { + hhDoor* localMoveMaster = NULL; + + if( !GetAirLockMaster() ) { + return; + } + + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node == this ) { + continue; + } + + if( !node->moveMaster->IsType(hhDoor::Type) ) { + continue; + } + + localMoveMaster = static_cast( node->moveMaster ); + //if( node != localMoveMaster ) { + // continue; + //} + + localMoveMaster->CancelReturnToPos1(); + if( !localMoveMaster->IsOpen() ) { + localMoveMaster->Open(); + } + } +} + +//-------------------------------- +// hhDoor::CancelReturnToPos1 +//-------------------------------- +void hhDoor::CancelReturnToPos1() { + for( idMover_Binary* slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + } +} + +//-------------------------------- +// hhDoor::SetBuddiesShaderParm +//-------------------------------- +void hhDoor::SetBuddiesShaderParm( int parm, float value ) { + idEntity* buddy = NULL; + + for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) { + if( !buddyNames[ix].Length() ) { + continue; + } + + buddy = gameLocal.FindEntity( buddyNames[ix].c_str() ); + if( !buddy ) { + continue; + } + + buddy->SetShaderParm( parm, value ); + } +} + +//-------------------------------- +// hhDoor::ToggleBuddiesShaderParm +//-------------------------------- +void hhDoor::ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay) { + SetBuddiesShaderParm( parm, firstValue ); + PostEventMS( &EV_SetBuddiesShaderParm, toggleDelay, parm, secondValue ); +} + +//-------------------------------- +// hhDoor::Event_SetBuddiesShaderParm +//-------------------------------- +void hhDoor::Event_SetBuddiesShaderParm( int parm, float value ) { + SetBuddiesShaderParm( parm, value ); +} + +//-------------------------------- +// hhDoor::Event_ReturnToPos1 +//-------------------------------- +void hhDoor::Event_ReturnToPos1( void ) { + + if ( !ForcedOpen() ) { + idDoor::Event_ReturnToPos1(); + } + +} // Event_ReturnToPos1() + +//-------------------------------- +// hhDoor::Event_Reached_BinaryMover +//-------------------------------- +void hhDoor::Event_Reached_BinaryMover( void ) { + if ( moverState == MOVER_2TO1 ) { + SetBlocked(false); + ActivatePrefixed("triggerClosed", this); + } else if (moverState == MOVER_1TO2) { + ActivatePrefixed("triggerOpened", this); + } + + if ( moverState == MOVER_1TO2 ) { + // reached pos2 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + // HUMANHEAD aob - removed + //if ( moveMaster == this ) { + //StartSound( "snd_opened", SND_CHANNEL_ANY ); + //} + //HUMANHEAD END + + SetMoverState( MOVER_POS2, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); + + UpdateBuddies(1); + + if( enabled && wait >= 0 && !spawnArgs.GetBool("toggle") ) { + // return to pos1 after a delay + //HUMANHEAD: aob + CancelReturnToPos1(); + //HUMANHEAD END + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + } + + // fire targets + ActivateTargets( moveMaster->GetActivator() ); + } else if ( moverState == MOVER_2TO1 ) { + // reached pos1 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + SetMoverState( MOVER_POS1, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); + + UpdateBuddies(0); + + // close areaportals + if ( moveMaster == this ) { + ProcessEvent( &EV_Mover_ClosePortal ); + } + + if( GetAirLockMaster() ) { + // Mark the other team members as unlocked + for( hhDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as unlocked when door closes + node->SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } + } + } else { + gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); + } +} + +/* +================ +hhDoor::Event_Touch +================ +*/ +void hhDoor::Event_Touch( idEntity *other, trace_t *trace ) { + if ( bShuttleDoors && ( ! other->IsType( idActor::Type ) || ! reinterpret_cast ( other )->InVehicle() ) ) { + if ( gameLocal.time > nextSndTriggerTime ) { + StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + return; + } + + idDoor::Event_Touch( other, trace ); +} diff --git a/neo/prey/Prey/game_door.h b/neo/prey/Prey/game_door.h new file mode 100644 index 00000000..d296c845 --- /dev/null +++ b/neo/prey/Prey/game_door.h @@ -0,0 +1,69 @@ +#ifndef __PREY_DOOR_H__ +#define __PREY_DOOR_H__ + +class hhDoor : public idDoor { + CLASS_PROTOTYPE( hhDoor ); + +public: + void Spawn(); + virtual ~hhDoor(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Use_BinaryMover( idEntity *activator ); + virtual void GotoPosition1(); + virtual void GotoPosition2(); + + //HUMANHEAD: aob + virtual void GotoPosition1( float wait ); + virtual void GotoPosition2( float wait ); + //HUMANHEAD END + + ID_INLINE const char* GetAirLockTeamName() const { return airlockTeamName.c_str(); } + ID_INLINE hhDoor* GetAirLockMaster() const { return airlockMaster; } + void Open(); + void Close(); + bool CanOpen() const; + bool CanClose() const; + bool IsClosed() const; + + bool OnMyMoveTeam( hhDoor* doorPiece ) const; + void CopyTeamInfoToMoveMaster( hhDoor* master ); + + void CancelReturnToPos1(); + + void SetBuddiesShaderParm( int parm, float value ); + void ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay ); + + bool ForcedOpen() const { return( forcedOpen ); }; + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + idEntity* DetermineTeamMaster( const char* teamName ); + void JoinAirLockTeam( hhDoor *master ); + void VerifyAirlockTeamStatus(); + + void ForceAirLockTeamClosed(); + void ForceAirLockTeamOpen(); + +protected: + void Event_ReturnToPos1( void ); // overridden to stop it moving when closed + void Event_SetBuddiesShaderParm( int parm, float value ); + void Event_Reached_BinaryMover( void ); + virtual void Event_Touch( idEntity *other, trace_t *trace ); + +public: + idLinkList airlockTeam; + +protected://More airLock stuff + idStr airlockTeamName; + hhDoor* airlockMaster; + + bool openWhenDead; // Do we open when dead? + bool forcedOpen; // Is the door forced open + float airLockSndWait; + float nextAirLockSnd; + bool bShuttleDoors; +}; + +#endif diff --git a/neo/prey/Prey/game_eggspawner.cpp b/neo/prey/Prey/game_eggspawner.cpp new file mode 100644 index 00000000..c9b14dc3 --- /dev/null +++ b/neo/prey/Prey/game_eggspawner.cpp @@ -0,0 +1,281 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhEggSpawner +// +// When activated, shoots out an egg along it's X axis +//========================================================================== + +#define MAX_HATCH_TIME (SEC2MS(10)) + +CLASS_DECLARATION(hhAnimatedEntity, hhEggSpawner) + EVENT( EV_PlayIdle, hhEggSpawner::Event_PlayIdle) + EVENT( EV_Activate, hhEggSpawner::Event_Activate) +END_CLASS + +void hhEggSpawner::Spawn(void) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + fl.takedamage = true; + + idleAnim = GetAnimator()->GetAnim("idle"); + hatchAnim = GetAnimator()->GetAnim("launch"); + painAnim = GetAnimator()->GetAnim("pain"); + + PostEventMS(&EV_PlayIdle, 0); +} + +void hhEggSpawner::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( hatchAnim ); + savefile->WriteInt( painAnim ); +} + +void hhEggSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( hatchAnim ); + savefile->ReadInt( painAnim ); +} + +void hhEggSpawner::SpawnEgg(idEntity *activator) { + idVec3 dir = GetPhysics()->GetAxis()[0]; + idVec3 offset = spawnArgs.GetVector("offset_spawn"); + float power = spawnArgs.GetFloat("power"); + + // Play anim + if (hatchAnim) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, hatchAnim, gameLocal.time, 0); + int ms = GetAnimator()->GetAnim( hatchAnim )->Length(); + PostEventMS(&EV_PlayIdle, ms); + } + + idDict args; + args.Clear(); + args.SetVector( "origin", GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis() ); + hhEgg *egg = static_cast(gameLocal.SpawnObject(spawnArgs.GetString("def_egg"), &args)); + if (egg) { + egg->GetPhysics()->SetLinearVelocity(dir * power); + egg->SetActivator(activator); + + // Copy our targets + for ( int i = 0; i < targets.Num(); i++ ) { + egg->targets.AddUnique(targets[i]); + } + } + + StartSound( "snd_spawn", SND_CHANNEL_ANY, 0, true, NULL ); +} + +void hhEggSpawner::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Don't actually take damage, but give feedback + if (painAnim) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 0); + int ms = GetAnimator()->GetAnim( painAnim )->Length(); + PostEventMS(&EV_PlayIdle, ms); + } +} + +void hhEggSpawner::Event_PlayIdle() { + if (idleAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 200); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 200); + } +} + +void hhEggSpawner::Event_Activate(idEntity *activator) { + SpawnEgg(activator); +} + + +//========================================================================== +// +// hhEgg +// +// Moveable that spawns a creature on impact +//========================================================================== + +const idEventDef EV_Hatch(""); + +CLASS_DECLARATION(hhMoveable, hhEgg) + EVENT( EV_Activate, hhEgg::Event_Activate) + EVENT( EV_Hatch, hhEgg::Event_Hatch) +END_CLASS + +void hhEgg::Spawn(void) { + enemy = NULL; + bHatched = false; + bHatching = false; + const char *tableName = spawnArgs.GetString("table_hatch"); + table = static_cast(declManager->FindType( DECL_TABLE, tableName, true )); + hatchTime = -1.0f; + + PostEventSec( &EV_Activate, spawnArgs.GetFloat("secondsBeforeHatch"), this ); +} + +void hhEgg::Save(idSaveGame *savefile) const { + + savefile->WriteBool( bHatching ); + savefile->WriteBool( bHatched ); + + savefile->WriteFloat( deformAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( deformAlpha.GetDuration() ); + savefile->WriteFloat( deformAlpha.GetStartValue() ); + savefile->WriteFloat( deformAlpha.GetEndValue() ); + savefile->WriteFloat( hatchTime ); + + enemy.Save(savefile); +} + +void hhEgg::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadBool( bHatching ); + savefile->ReadBool( bHatched ); + + savefile->ReadFloat( set ); // idInterpolate + deformAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + deformAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + deformAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + deformAlpha.SetEndValue( set ); + + savefile->ReadFloat( hatchTime ); + + const char *tableName = spawnArgs.GetString("table_hatch"); + table = static_cast(declManager->FindType( DECL_TABLE, tableName, true )); + + enemy.Restore(savefile); +} + + +void hhEgg::SetActivator(idEntity *activator) { + if (activator && activator->IsType(idActor::Type)) { + enemy = static_cast(activator); + } +} + +void hhEgg::Ticker() { + if (bHatching) { + // over time, send different parms into deformation + if (deformAlpha.IsDone(gameLocal.time)) { + Hatch(); + } + else { + float alpha = deformAlpha.GetCurrentValue(gameLocal.time); + float value = table->TableLookup(alpha); + SetShaderParm(SHADERPARM_ANY_DEFORM, DEFORMTYPE_VIBRATE); + SetShaderParm(SHADERPARM_ANY_DEFORM_PARM1, value); + } + } +} + +bool hhEgg::Collide( const trace_t &collision, const idVec3 &velocity ) { + AttemptToPlayBounceSound( collision, velocity ); + + if (spawnArgs.GetBool("spawnOnImpact")) { //obs + Hatch(); + return true; + } + return false; +} + +void hhEgg::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + Hatch(); +} + +bool hhEgg::SpawnHatchling(const char *monsterName, const idVec3 &spawnLocation) { + idDict args; + args.Clear(); + args.SetVector( "origin", spawnLocation ); + args.SetFloat( "angle", GetAxis().ToAngles().yaw ); + args.Set( "trigger_anim", "birth" ); + hhMonsterAI *monster = static_cast(gameLocal.SpawnObject(monsterName, &args)); + + if (monster) { + // Set enemy + if (enemy.IsValid()) { + monster->SetEnemy(enemy.GetEntity()); + } + + // Copy our targets + for ( int i = 0; i < targets.Num(); i++ ) { + monster->targets.AddUnique(targets[i]); + } + + monster->PostEventSec( &EV_Activate, 0, this ); + } + + return monster != NULL; +} + +void hhEgg::Hatch() { + if (!bHatched) { + if (hatchTime == -1.0f) { + hatchTime = gameLocal.time; + } + // Spawn hatchling + const char *monsterName = spawnArgs.GetString("def_hatchling", NULL); + if (monsterName) { + idVec3 spawnLocation = GetPhysics()->GetOrigin(); + + const float hatchOffsetDistance = 50.0f; + bool spawned = false; + idVec3 offset; + idVec3 offsetLocation; + trace_t trace; + memset(&trace, 0, sizeof(trace_t)); + + if (hhUtils::EntityDefWillFit(monsterName, spawnLocation, mat3_identity, MASK_MONSTERSOLID, this)) { + //gameRenderWorld->DebugArrow(colorGreen, spawnLocation, offsetLocation, 10, 10000); + spawned = SpawnHatchling(monsterName, spawnLocation); + } + + if (!spawned) { // Creature wouldn't fit, try again later + CancelEvents(&EV_Hatch); + if (gameLocal.time - hatchTime > MAX_HATCH_TIME) { + // Give up after 15 seconds of struggling + ActivateTargets(this); + // Spawn gibs from monster + const idDict *dict = gameLocal.FindEntityDefDict(monsterName, false); + if (dict && dict->FindKey("def_gibdebrisspawner")) { + GetPhysics()->SetLinearVelocity( vec3_zero ); //HUMANHEAD jsh PCF zero out velocity to prevent debris huge translation + hhUtils::SpawnDebrisMass(dict->GetString("def_gibdebrisspawner"), this); + } + } else { + //idVec3 dir = hhUtils::RandomVector(); + //gameRenderWorld->DebugArrow(colorGreen, spawnLocation, spawnLocation + dir * 50.0f, 10, 250); + GetPhysics()->AddForce(0, GetPhysics()->GetOrigin(), hhUtils::RandomVector() * idMath::ClampFloat(0x1000000, 0x100000000, (0x1000000 * ((gameLocal.time - hatchTime) / 25.0f)))); + PostEventMS(&EV_Hatch, 250); + return; + } + } + } + + hatchTime = -1.0f; + bHatched = true; + GetPhysics()->SetContents(0); + PostEventMS(&EV_Remove, 0); + + StartSound( "snd_hatch", SND_CHANNEL_ANY, 0, true, NULL ); + GetPhysics()->SetLinearVelocity( vec3_zero ); //HUMANHEAD jsh PCF zero out velocity to prevent debris huge translation + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_afterbirth"), this); + } +} + +void hhEgg::Event_Activate(idEntity *activator) { + if (!bHatching) { + bHatching = true; + int msForTable = SEC2MS(spawnArgs.GetFloat("secondsToHatch")); + deformAlpha.Init(gameLocal.time, msForTable, 0.0f, 1.0f); + BecomeActive(TH_TICKER); + } +} + +void hhEgg::Event_Hatch(void) { + Hatch(); +} diff --git a/neo/prey/Prey/game_eggspawner.h b/neo/prey/Prey/game_eggspawner.h new file mode 100644 index 00000000..fe534086 --- /dev/null +++ b/neo/prey/Prey/game_eggspawner.h @@ -0,0 +1,53 @@ + +#ifndef __GAME_EGGSPAWNER_H__ +#define __GAME_EGGSPAWNER_H__ + +class hhEggSpawner : public hhAnimatedEntity { + CLASS_PROTOTYPE( hhEggSpawner ); +public: + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SpawnEgg(idEntity *activator); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + void Event_Activate(idEntity *activator); + void Event_PlayIdle(); + +protected: + int idleAnim; + int hatchAnim; + int painAnim; +}; + + +class hhEgg : public hhMoveable { + CLASS_PROTOTYPE( hhEgg ); +public: + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Ticker(); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Hatch(); + void SetActivator(idEntity *activator); + +protected: + bool SpawnHatchling(const char *monsterName, const idVec3 &spawnLocation); + void Event_Activate(idEntity *activator); + void Event_Hatch(); + +protected: + bool bHatching; + bool bHatched; + idInterpolate deformAlpha; + const idDeclTable * table; + idEntityPtr enemy; + float hatchTime; +}; + +#endif diff --git a/neo/prey/Prey/game_energynode.cpp b/neo/prey/Prey/game_energynode.cpp new file mode 100644 index 00000000..e83f6266 --- /dev/null +++ b/neo/prey/Prey/game_energynode.cpp @@ -0,0 +1,126 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_EnableNode( "enableNode", NULL ); +const idEventDef EV_DisableNode( "disableNode", NULL ); + +CLASS_DECLARATION( idStaticEntity, hhEnergyNode ) + EVENT( EV_EnableNode, hhEnergyNode::Event_Enable ) + EVENT( EV_DisableNode, hhEnergyNode::Event_Disable ) +END_CLASS + +hhEnergyNode::hhEnergyNode(void) +:disabled(false) +{} + +hhEnergyNode::~hhEnergyNode(void) { + Event_Disable(); +} + + +void hhEnergyNode::Save(idSaveGame *savefile) const { + savefile->WriteBool( disabled ); + savefile->WriteVec3( leechPoint ); + + energyFx.Save( savefile ); +} + +void hhEnergyNode::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( disabled ); + savefile->ReadVec3( leechPoint ); + + energyFx.Restore( savefile ); +} + +void hhEnergyNode::Spawn( void ) { + fl.networkSync = true; + GetPhysics()->SetContents( CONTENTS_SOLID ); + + leechPoint = GetAxis()*spawnArgs.GetVector("leechPoint")+GetOrigin(); + + fl.clientEvents = true; + PostEventMS(&EV_EnableNode, 1); +} + +void hhEnergyNode::LeechTrigger(idEntity *activator, const char* type) { + idStr name = spawnArgs.GetString(type); + if ( name != "" ) { + idEntity* ent = gameLocal.FindEntity(name); + if ( ent ) { + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + ent->TriggerGuis(); + } + } +} + +void hhEnergyNode::Finish() { + Event_Disable(); + + if ( spawnArgs.GetInt("infinite","0") || gameLocal.isMultiplayer ) { // delay before reenabling -cjr + PostEventSec( &EV_EnableNode, spawnArgs.GetFloat( "reenableDelay", "20" ) ); + } +} + +void hhEnergyNode::Event_Enable() { + disabled = false; + + const idDict *energyDef = NULL; + const char* str = spawnArgs.GetString( "def_energy" ); + if ( str && str[0] ) { + energyDef = gameLocal.FindEntityDefDict( str ); + } + + if ( energyDef ) { + hhFxInfo fxInfo; + if (IsBound() || spawnArgs.GetBool("force_bind")) { + fxInfo.SetEntity( this ); // Only bind if we will be moving + } + energyFx = SpawnFxLocal( energyDef->GetString("fx_node"), leechPoint, GetAxis(), &fxInfo, true ); + + idVec3 color = energyDef->GetVector("nodeColor"); + SetShaderParm( SHADERPARM_RED, color.x ); + SetShaderParm( SHADERPARM_GREEN, color.y ); + SetShaderParm( SHADERPARM_BLUE, color.z ); + + StartSound( "snd_activate", SND_CHANNEL_ANY ); + StartSound( "snd_idle", SND_CHANNEL_BODY ); + } +} + +void hhEnergyNode::Event_Disable() { + disabled = true; + if( energyFx.IsValid() ) { + SAFE_REMOVE(energyFx); + } + + SetShaderParm( SHADERPARM_RED, 0 ); + SetShaderParm( SHADERPARM_GREEN, 0 ); + SetShaderParm( SHADERPARM_BLUE, 0 ); + + StopSound( SND_CHANNEL_BODY ); +} + +void hhEnergyNode::WriteToSnapshot( idBitMsgDelta &msg ) const { + idStaticEntity::WriteToSnapshot(msg); + + msg.WriteBits(disabled, 1); +} + +void hhEnergyNode::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idStaticEntity::ReadFromSnapshot(msg); + + bool snapDisabled = !!msg.ReadBits(1); + if (snapDisabled != disabled) { + if (snapDisabled) { + Event_Disable(); + } + else { + Event_Enable(); + } + } +} diff --git a/neo/prey/Prey/game_energynode.h b/neo/prey/Prey/game_energynode.h new file mode 100644 index 00000000..ca864be0 --- /dev/null +++ b/neo/prey/Prey/game_energynode.h @@ -0,0 +1,35 @@ +#ifndef __GAME_ENERGYNODE_H__ +#define __GAME_ENERGYNODE_H__ + +class hhEnergyNode : public idStaticEntity { +public: + CLASS_PROTOTYPE( hhEnergyNode ); + + hhEnergyNode(void); + ~hhEnergyNode(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + inline bool CanLeech() { return !disabled; } + void LeechTrigger(idEntity *activator, const char* type); + void Finish(); + + virtual void Event_Enable(); + virtual void Event_Disable(); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + idVec3 leechPoint; + +protected: + bool disabled; + + idEntityPtr energyFx; +}; + +#endif // __GAME_ENERGYNODE_H__ diff --git a/neo/prey/Prey/game_entityfx.cpp b/neo/prey/Prey/game_entityfx.cpp new file mode 100644 index 00000000..def5eb29 --- /dev/null +++ b/neo/prey/Prey/game_entityfx.cpp @@ -0,0 +1,637 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idEntityFx, hhEntityFx ) + EVENT( EV_Activate, hhEntityFx::Event_Trigger ) + EVENT( EV_Fx_KillFx, hhEntityFx::Event_ClearFx ) +END_CLASS + +/* +================ +hhEntityFx::hhEntityFx +================ +*/ +hhEntityFx::hhEntityFx() { + // HUMANHEAD nla + setFxInfo = false; + // HUMANHEAD + +// HUMANHEAD bg + restartActive = false; +// HUMANHEAD END +} + +/* +================ +hhEntityFx::~hhEntityFx +================ +*/ +hhEntityFx::~hhEntityFx() { + //HUMANHEAD: aob - need to shut everything down + Event_ClearFx(); + //HUMANHEAD END +} + +void hhEntityFx::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( fxInfo ); + savefile->WriteBool( setFxInfo ); + savefile->WriteBool( removeWhenDone ); +// HUMANHEAD bg + savefile->WriteBool( restartActive ); +// HUMANHEAD END +} + +void hhEntityFx::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( fxInfo ); + savefile->ReadBool( setFxInfo ); + savefile->ReadBool( removeWhenDone ); +// HUMANHEAD bg + savefile->ReadBool( restartActive ); +// HUMANHEAD END +} + +void hhEntityFx::WriteToSnapshot( idBitMsgDelta &msg ) const { + idEntityFx::WriteToSnapshot(msg); + + fxInfo.WriteToSnapshot(msg); +} + +void hhEntityFx::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idEntityFx::ReadFromSnapshot(msg); + + fxInfo.ReadFromSnapshot(msg); +} + +/* +================ +hhEntityFx::CleanUpSingleAction +================ +*/ +void hhEntityFx::CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ) { + idEntityFx::CleanUpSingleAction( fxaction, laction ); + + // HUMANHEAD pdm: sound "duration" support + if ( fxaction.type == FX_SOUND && fxaction.sibling == -1 && laction.soundStarted ) { + const idSoundShader *def = declManager->FindSound( fxaction.data ); + refSound.referenceSound->StopSound( SND_CHANNEL_ANY ); + } + + if ( fxaction.type == FX_PARTICLE && fxaction.sibling == -1 ) { + laction.particleSystem = NULL; + laction.particleStartTime = -1; + } + // HUMANHEAD END +} + +/* +================ +hhEntityFx::Hide +================ +*/ +void hhEntityFx::Hide() { + CleanUp(); +} + +/* +================ +hhEntityFx::Run + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================ +*/ +void hhEntityFx::Run( int time ) { + int ieff, j; + idEntity *ent = NULL; + const idDict *projectileDef = NULL; + idProjectile *projectile = NULL; + + if ( !fxEffect || IsHidden() ) { + return; + } + + for( ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) { + const idFXSingleAction& fxaction = fxEffect->events[ieff]; + idFXLocalAction& laction = actions[ieff]; + + // + // if we're currently done with this one + // + if ( laction.start == -1 ) { + continue; + } + + // + // see if it's delayed + // + if ( laction.delay ) { + if ( laction.start + (time - laction.start) < laction.start + (laction.delay * 1000) ) { + continue; + } + } + + // + // each event can have it's own delay and restart + // + int actualStart = laction.delay ? laction.start + (int)( laction.delay * 1000 ) : laction.start; + float pct = (float)( time - actualStart ) / (1000 * fxaction.duration ); + if ( pct >= 1.0f ) { + laction.start = -1; + float totalDelay = 0.0f; + if ( fxaction.restart ) { + if ( fxaction.random1 || fxaction.random2 ) { + totalDelay = fxaction.random1 + gameLocal.random.RandomFloat() * (fxaction.random2 - fxaction.random1); + } else { + totalDelay = fxaction.delay; + } + laction.delay = totalDelay; + laction.start = time; + } + continue; + } + + if ( fxaction.fire.Length() ) { + for( j = 0; j < fxEffect->events.Num(); j++ ) { + if ( fxEffect->events[j].name.Icmp( fxaction.fire ) == 0 ) { + actions[j].delay = 0; + } + } + } + + idFXLocalAction *useAction = NULL; + if ( fxaction.sibling == -1 ) { + useAction = &laction; + } else { + useAction = &actions[fxaction.sibling]; + } + assert( useAction ); + + switch( fxaction.type ) { + case FX_ATTACHLIGHT: + case FX_LIGHT: { + if ( useAction->lightDefHandle == -1 ) { + if ( fxaction.type == FX_LIGHT ) { + memset( &useAction->renderLight, 0, sizeof( renderLight_t ) ); + //HUMANHEAD: bjk once again fixing aob code + useAction->renderLight.axis = DetermineAxis( fxaction ); + useAction->renderLight.origin = GetOrigin() + fxaction.offset * useAction->renderLight.axis; + useAction->renderLight.axis = hhUtils::SwapXZ( useAction->renderLight.axis ); + //HUMANHEAD END + useAction->renderLight.lightRadius[0] = fxaction.lightRadius; + useAction->renderLight.lightRadius[1] = fxaction.lightRadius; + useAction->renderLight.lightRadius[2] = fxaction.lightRadius; + useAction->renderLight.shader = declManager->FindMaterial( fxaction.data, false ); + useAction->renderLight.shaderParms[ SHADERPARM_RED ] = fxaction.lightColor.x; + useAction->renderLight.shaderParms[ SHADERPARM_GREEN ] = fxaction.lightColor.y; + useAction->renderLight.shaderParms[ SHADERPARM_BLUE ] = fxaction.lightColor.z; + useAction->renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + useAction->renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); + useAction->renderLight.referenceSound = refSound.referenceSound; + useAction->renderLight.pointLight = true; + if ( fxaction.noshadows ) { + useAction->renderLight.noShadows = true; + } + useAction->lightDefHandle = gameRenderWorld->AddLightDef( &useAction->renderLight ); + } + if ( fxaction.noshadows ) { + for( j = 0; j < fxEffect->events.Num(); j++ ) { + idFXLocalAction& laction2 = actions[j]; + if ( laction2.modelDefHandle != -1 ) { + laction2.renderEntity.noShadow = true; + } + } + } + } else if ( fxaction.trackOrigin ) { + //HUMANHEAD: bjk + useAction->renderLight.axis = DetermineAxis( fxaction ); + useAction->renderLight.origin = GetOrigin() + fxaction.offset * useAction->renderLight.axis; + useAction->renderLight.axis = hhUtils::SwapXZ( useAction->renderLight.axis ); + gameRenderWorld->UpdateLightDef( useAction->lightDefHandle, &useAction->renderLight ); + //HUMANHEAD END + } + ApplyFade( fxaction, *useAction, time, actualStart ); + break; + } + case FX_SOUND: { + if ( !useAction->soundStarted ) { + useAction->soundStarted = true; + const idSoundShader *shader = declManager->FindSound(fxaction.data); + StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + for( j = 0; j < fxEffect->events.Num(); j++ ) { + idFXLocalAction& laction2 = actions[j]; + if ( laction2.lightDefHandle != -1 ) { + laction2.renderLight.referenceSound = refSound.referenceSound; + gameRenderWorld->UpdateLightDef( laction2.lightDefHandle, &laction2.renderLight ); + } + } + } + break; + } + case FX_DECAL: { + if ( !useAction->decalDropped ) { + useAction->decalDropped = true; + // HUMANHEAD pdm: Increased depth to from 8 to 25 + //TODO: Expose depth and parrallel to the FX files + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 25.0f, true, fxaction.size, fxaction.data ); + } + break; + } + case FX_SHAKE: { + if ( !useAction->shakeStarted ) { + idDict args; + args.Clear(); + args.SetFloat( "kick_time", fxaction.shakeTime ); + args.SetFloat( "kick_amplitude", fxaction.shakeAmplitude ); + for ( j = 0; j < gameLocal.numClients; j++ ) { + idPlayer *player = gameLocal.GetClientByNum( j ); + if ( player && ( player->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr() < Square( fxaction.shakeDistance ) ) { + if ( !gameLocal.isMultiplayer || !fxaction.shakeIgnoreMaster || GetBindMaster() != player ) { + player->playerView.DamageImpulse( fxaction.offset, &args ); + } + } + } + if ( fxaction.shakeImpulse != 0.0f && fxaction.shakeDistance != 0.0f ) { + idEntity *ignore_ent = NULL; + if ( gameLocal.isMultiplayer ) { + ignore_ent = this; + if ( fxaction.shakeIgnoreMaster ) { + ignore_ent = GetBindMaster(); + } + } + // lookup the ent we are bound to? + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), fxaction.shakeDistance, fxaction.shakeImpulse, this, ignore_ent, 1.0f, true ); + } + useAction->shakeStarted = true; + } + break; + } + case FX_ATTACHENTITY: + case FX_MODEL: { + if ( useAction->modelDefHandle == -1 ) { + memset( &useAction->renderEntity, 0, sizeof( renderEntity_t ) ); + //HUMANHEAD: aob - rotated offset by axis + useAction->renderEntity.axis = DetermineAxis( fxaction ); + useAction->renderEntity.origin = GetOrigin() + fxaction.offset * useAction->renderEntity.axis; + useAction->renderEntity.axis = hhUtils::SwapXZ( useAction->renderEntity.axis ); + useAction->renderEntity.weaponDepthHack = renderEntity.weaponDepthHack; + useAction->renderEntity.onlyVisibleInSpirit = renderEntity.onlyVisibleInSpirit; + useAction->renderEntity.onlyInvisibleInSpirit = renderEntity.onlyInvisibleInSpirit; // tmj + useAction->renderEntity.allowSurfaceInViewID = renderEntity.allowSurfaceInViewID; // bjk + //HUMANHEAD END + useAction->renderEntity.hModel = renderModelManager->FindModel( fxaction.data ); + // HUMANHEAD pdm: allow color on fx, but they'll be the same for all particles + if ( renderEntity.shaderParms[SHADERPARM_RED] != 0.0f || + renderEntity.shaderParms[SHADERPARM_GREEN] != 0.0f || + renderEntity.shaderParms[SHADERPARM_BLUE] != 0.0f) { + useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = renderEntity.shaderParms[SHADERPARM_RED]; + useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = renderEntity.shaderParms[SHADERPARM_GREEN]; + useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = renderEntity.shaderParms[SHADERPARM_BLUE]; + } + else { + useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + } + useAction->renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); + useAction->renderEntity.shaderParms[3] = 1.0f; + useAction->renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.RandomFloat(); //HUMANHEAD bjk + if ( useAction->renderEntity.hModel ) { + useAction->renderEntity.bounds = useAction->renderEntity.hModel->Bounds( &useAction->renderEntity ); + } + useAction->modelDefHandle = gameRenderWorld->AddEntityDef( &useAction->renderEntity ); + } else if ( fxaction.trackOrigin ) { + //HUMANHEAD: aob - rotated offset by axis + useAction->renderEntity.axis = DetermineAxis( fxaction ); + if (fxaction.offset.x || fxaction.offset.y || fxaction.offset.z) { + useAction->renderEntity.origin = GetOrigin() + fxaction.offset * useAction->renderEntity.axis; + } + else { + useAction->renderEntity.origin = GetOrigin(); + } + useAction->renderEntity.axis = hhUtils::SwapXZ( useAction->renderEntity.axis ); + gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity ); + //HUMANHEAD END + } + ApplyFade( fxaction, *useAction, time, actualStart ); + break; + } + case FX_PARTICLE: { + //HUMANHEAD: aob + //rww - kind of a hack, but otherwise the timing is off, which causes issues in mp. + if (useAction->particleStartTime == -2) { + useAction->particleStartTime = gameLocal.time; + } + + if( !useAction->particleSystem && fxaction.data.Length() ) { + useAction->particleSystem = static_cast( declManager->FindType( DECL_PARTICLE, fxaction.data, false ) ); + } else if( useAction->particleStartTime >= 0 && useAction->particleSystem ) { + idMat3 axis = DetermineAxis( fxaction ); + if( !gameLocal.smokeParticles->EmitSmoke(useAction->particleSystem, useAction->particleStartTime, gameLocal.random.RandomFloat(), GetOrigin() + fxaction.offset * axis, hhUtils::SwapXZ(axis)) ) { + useAction->particleStartTime = -1; + } + } + // HUMANHEAD END + break; + } + case FX_LAUNCH: { + //HUMANHEAD rww - if this is a client ent then assert. + assert(!fl.clientEntity); + + if ( gameLocal.isClient ) { + // client never spawns entities outside of ClientReadSnapshot + useAction->launched = true; + break; + } + if ( !useAction->launched ) { + useAction->launched = true; + projectile = NULL; + // FIXME: may need to cache this if it is slow + projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false ); + if ( !projectileDef ) { + gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() ); + } else { + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); + if ( ent && ent->IsType( idProjectile::Type ) ) { + projectile = ( idProjectile * )ent; + projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] ); + projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin ); + } + } + } + break; + } + } + } +} + +/* +================ +hhEntityFx::DetermineAxis + +HUMANHEAD: aob +================ +*/ +idMat3 hhEntityFx::DetermineAxis( const idFXSingleAction& fxaction ) { + idVec3 fxDir; + + if( fxaction.explicitAxis ) { + if (fxaction.useAxis == AXIS_CUSTOMLOCAL) { + // When customlocal is set, axis is the specifed axis, untransformed by the entities axis + return fxaction.dir.ToMat3(); + } + return (fxaction.dir[0] * GetAxis()[0] + fxaction.dir[1] * GetAxis()[1] + fxaction.dir[2] * GetAxis()[2]).ToMat3(); + } else if( fxInfo.GetAxisFor(fxaction.useAxis, fxDir) ) { + return fxDir.ToMat3(); + } + + return GetAxis(); +} + +/* +================ +hhEntityFx::Nozzle + +HUMANHEAD: aob - used to toggle particle systems +================ +*/ +void hhEntityFx::Nozzle( bool bOn ) { + if ( !fxEffect ) { + return; + } + + if( bOn && !IsActive( TH_THINK ) ) { + Event_Trigger( NULL ); + } + + if( !bOn ) { + //Canceling events incase we are toggled before our actvate event has fired. + CancelEvents( &EV_Activate ); + + if( IsActive( TH_THINK ) ) { + Event_ClearFx(); + } + } +} + +/* +================ +hhEntityFx::DormantBegin + +//HUMANHEAD: aob +================ +*/ +void hhEntityFx::DormantBegin() { + idEntityFx::DormantBegin(); + + //HUMANHEAD: aob - used CleanUp directly so 'started' doesn't get reset + //Stop(); + CleanUp(); + //HUMANHEAD END + + //want to make sure we can trigger these back on + nextTriggerTime = -1; +} + +/* +================ +hhEntityFx::DormantEnd + +//HUMANHEAD: aob +================ +*/ +void hhEntityFx::DormantEnd() { + idEntityFx::DormantEnd(); + + if ( started >= 0 ) { + CreateFx( this ); + } +} + +/* +================ +hhEntityFx::Event_Trigger +================ +*/ +void hhEntityFx::Event_Trigger( idEntity *activator ) { + if ( gameLocal.time < nextTriggerTime ) { + return; + } + +// HUMANHEAD bg: Special handling for "restart" fx enable/disable. + if ( restartActive && (activator != this) ) { + CancelEvents( &EV_Fx_Action ); + CancelEvents( &EV_Activate ); + CancelEvents( &EV_Fx_KillFx ); + Stop(); + CleanUp(); + BecomeInactive( TH_THINK ); + restartActive = false; + return; + } + if ( !restartActive && spawnArgs.GetFloat( "restart" ) ) { + restartActive = true; + } +// HUMANHEAD END + + //HUMANHEAD: aob + if( spawnArgs.GetBool("toggle") && IsActive(TH_THINK) ) { + ProcessEvent( &EV_Fx_KillFx ); + return; + } + //HUMANHEAD END + + //HUMANHEAD: aob - moved logic to helper function so I can call code specifically + CreateFx( activator ); + //HUMANHEAD END +} + +/* +================ +hhEntityFx::CreateFx + +HUMANHEAD: aob +================ +*/ +void hhEntityFx::CreateFx( idEntity *activator ) { + if ( g_skipFX.GetBool() ) { + return; + } + + float fxActionDelay; + const char *fx; + + if ( spawnArgs.GetString( "fx", "", &fx) ) { + Setup( fx ); + Start( gameLocal.time ); + //HUMANHEAD: aob - added so fx can be deleted at end of duration + if( RemoveWhenDone() ) { + PostEventMS( &EV_Remove, Duration() ); + } else { + PostEventMS( &EV_Fx_KillFx, Duration() ); + } + //HUMANHEAD END + BecomeActive( TH_THINK ); + } + + fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" ); + if ( fxActionDelay != 0.0f ) { + nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay ); + } else { + // prevent multiple triggers on same frame + nextTriggerTime = gameLocal.time + 1; + } + PostEventSec( &EV_Fx_Action, fxActionDelay, activator ); +} + +/* +================ +hhEntityFx::StartFx + +creates an fx entity, ONLY CALL THIS ON THE SERVER. -rww +================ +*/ +hhEntityFx *hhEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ) +{ + + if ( g_skipFX.GetBool() || !fx || !*fx ) { + return NULL; + } + + idDict args; + args.SetBool( "start", true ); + args.Set( "fx", fx ); + hhEntityFx *nfx = static_cast( gameLocal.SpawnEntityType( hhEntityFx::Type, &args ) ); + if ( nfx->Joint() && *nfx->Joint() ) { + nfx->BindToJoint( ent, nfx->Joint(), true ); + nfx->SetOrigin( vec3_origin ); + } else { + nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() ); + nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() ); + } + + if ( bind ) { + // never bind to world spawn + if ( ent != gameLocal.world ) { + nfx->Bind( ent, true ); + } + } + nfx->Show(); + return nfx; +} + +/* +================ +hhEntityFx::SetParticleShaderParm + Allow setting shader parms directly to particle renderentities + Note that this functionality isn't set to the default SetShaderParm() + to avoid breaking any existing code +================ +*/ +void hhEntityFx::SetParticleShaderParm( int parmnum, float value ) { + + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Warning( "shader parm index (%d) out of range", parmnum ); + return; + } + + for( int ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) { + const idFXSingleAction& fxaction = fxEffect->events[ieff]; + idFXLocalAction *useAction = NULL; + idFXLocalAction& laction = actions[ieff]; + if ( fxaction.sibling == -1 ) { + useAction = &laction; + } else { + useAction = &actions[fxaction.sibling]; + } + if ( !useAction ) { + continue; + } + switch( fxaction.type ) { + case FX_MODEL: { + useAction->renderEntity.shaderParms[parmnum] = value; + break; + } + } + } +} + +/* +================ +hhEntityFx::Event_ClearFx + + Clears any visual fx started when item(mob) was spawned +================ +*/ +void hhEntityFx::Event_ClearFx( void ) { + + if ( g_skipFX.GetBool() ) { + return; + } + + Stop(); + CleanUp(); + BecomeInactive( TH_THINK ); + + if ( spawnArgs.GetBool("test") ) { + PostEventMS( &EV_Activate, 0, this ); + } else { + if (!spawnArgs.GetBool("triggered")) { + float rest = spawnArgs.GetInt( "restart", "0" ); + if ( rest == 0.0f ) { + //HUMANHEAD: aob - added RemoveWhenDone and CancelEvents call + if( RemoveWhenDone() ) { + PostEventSec( &EV_Remove, 0.1f ); + } else { + CancelEvents( &EV_Fx_Action ); + } + //HUMANHEAD END + } else { + rest *= gameLocal.random.RandomFloat(); +// HUMANHEAD bg: Give ability for minimum restart time by adding offset. + rest += spawnArgs.GetFloat( "restartDelay", "0" ); +// HUMANHEAD END + PostEventSec( &EV_Activate, rest, this ); + } + } + } +} diff --git a/neo/prey/Prey/game_entityfx.h b/neo/prey/Prey/game_entityfx.h new file mode 100644 index 00000000..35980230 --- /dev/null +++ b/neo/prey/Prey/game_entityfx.h @@ -0,0 +1,62 @@ +#ifndef __HH_ENTITY_FX_H +#define __HH_ENTITY_FX_H + +class hhEntityFx : public idEntityFx { + CLASS_PROTOTYPE( hhEntityFx ); + +public: + hhEntityFx(); + virtual ~hhEntityFx(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //HUMANHEAD rww + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + //HUMANHEAD END + + virtual void Run( int time ); + + // HUMANHEAD nla + void SetUseAxis( fxAxis theAxis ) { }; + void SetFxInfo( const hhFxInfo &i ) { fxInfo = i; setFxInfo = true; removeWhenDone = fxInfo.RemoveWhenDone(); GetRenderEntity()->weaponDepthHack = fxInfo.UseWeaponDepthHack(); } + bool RemoveWhenDone() { return( removeWhenDone ); } + void RemoveWhenDone( bool remove ) { removeWhenDone = remove; } + void Toggle() { Nozzle( !IsActive(TH_THINK) ); } + void Nozzle( bool bOn ); + idMat3 DetermineAxis( const idFXSingleAction& fxaction ); + void CreateFx( idEntity *activator ); + + virtual void Hide(); + + static hhEntityFx *StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ); + void SetParticleShaderParm( int parmnum, float value ); + // HUMANHEAD END + +protected: + virtual void CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ); + virtual void DormantBegin(); + virtual void DormantEnd(); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_ClearFx( void ); + +protected: + // HUMANHEAD nla + enum fxAxis { AXIS_CURRENT, AXIS_NORMAL, AXIS_BOUNCE, AXIS_INCOMING, AXIS_CUSTOMLOCAL }; + // HUMANHEAD END + + // HUMANHEAD + hhFxInfo fxInfo; + bool setFxInfo; + bool removeWhenDone; + // HUMANHEAD END + + // HUMANHEAD bg + bool restartActive; + // HUMANHEAD END +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_entityspawner.cpp b/neo/prey/Prey/game_entityspawner.cpp new file mode 100644 index 00000000..d831dc91 --- /dev/null +++ b/neo/prey/Prey/game_entityspawner.cpp @@ -0,0 +1,117 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_SpawnEntity("spawnEntity", NULL, 'd'); + +CLASS_DECLARATION( idEntity, hhEntitySpawner ) + EVENT(EV_Activate, hhEntitySpawner::Event_Activate) + EVENT(EV_SpawnEntity, hhEntitySpawner::Event_SpawnEntity) +END_CLASS + +// +// Spawn() +// +void hhEntitySpawner::Spawn( void ) { + + maxSpawnCount = spawnArgs.GetInt("max_spawn", "-1"); // Default: Infinite + currSpawnCount = 0; + if(!spawnArgs.GetString("def_entity", "", entDefName)) { + gameLocal.Error("def_entity not specified for hhEntitySpawner"); + } + entSpawnArgs.Clear(); + idStr tmpStr; + idStr realKeyName; + + // Copy keys for monster + const idKeyValue *kv = spawnArgs.MatchPrefix("ent_", NULL); + while(kv) { + tmpStr = kv->GetKey(); + int usIndex = tmpStr.FindChar("ent_", '_'); + realKeyName = tmpStr.Mid(usIndex+1, strlen(kv->GetKey())-usIndex-1); + entSpawnArgs.Set(realKeyName, kv->GetValue()); + + kv = spawnArgs.MatchPrefix("ent_", kv); + } +} + +void hhEntitySpawner::Save(idSaveGame *savefile) const { + savefile->WriteString( entDefName ); + savefile->WriteDict( &entSpawnArgs ); + savefile->WriteInt( maxSpawnCount ); + savefile->WriteInt( currSpawnCount ); +} + +void hhEntitySpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadString( entDefName ); + savefile->ReadDict( &entSpawnArgs ); + savefile->ReadInt( maxSpawnCount ); + savefile->ReadInt( currSpawnCount ); +} + +// +// Event_Activate() +// +void hhEntitySpawner::Event_Activate(idEntity *activator) { + Event_SpawnEntity(); +} + +// +// Event_Activate() +// +void hhEntitySpawner::Event_SpawnEntity( void ) { + idVec3 entSize; + + // Can't spawn anymore + if(maxSpawnCount >= 0 && currSpawnCount >= maxSpawnCount) { + return; + } + + idDict args = entSpawnArgs;; + + args.SetVector( "origin", GetPhysics()->GetOrigin()); + args.SetMatrix("rotation", GetAxis()); + + // entity collision checks for seeing if we are going to collide with another entity on spawn + const idDict *entDef = gameLocal.FindEntityDefDict( entDefName, false ); + if ( !entDef ) { + if (!entDefName) { //HUMANHEAD rww + gameLocal.Error("NULL entDefName in hhEntitySpawner::Event_SpawnEntity\n"); + } + else { + gameLocal.Warning( "Unknown Def '%s'", entDefName ); + } + return; + } + entDef->GetVector( "size", "0", entSize ); + idBounds bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( max( entSize.x, entSize.y ) ); + idEntity* ents[MAX_GENTITIES]; + int numModels = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, ents, MAX_GENTITIES ); + for ( int i = 0; i < numModels ; i++ ) { + if( ents[i] && ents[i]->IsType(idActor::Type) ) { + idThread::ReturnInt( false ); + return; + } + } + + idEntity *e = gameLocal.SpawnObject(entDefName, &args); + + if(!e) { + gameLocal.Error("hhEntitySpawner: Failed to spawn entity def named: %s", entDefName); + return; + } + + // Copy the targets that our case has to our newly spawned entity + for( int i = 0; i < targets.Num(); i++ ) { + e->targets.AddUnique(targets[i]); + } + + if(e->IsType(idAI::Type)) { + static_cast(e)->viewAxis = GetAxis(); + } + + currSpawnCount++; + idThread::ReturnInt( true ); +} diff --git a/neo/prey/Prey/game_entityspawner.h b/neo/prey/Prey/game_entityspawner.h new file mode 100644 index 00000000..ef9cc1fd --- /dev/null +++ b/neo/prey/Prey/game_entityspawner.h @@ -0,0 +1,25 @@ +#ifndef GAME_ENTITYSPAWNER_H +#define GAME_ENTITYSPAWNER_H + +// +// hhEntitySpawner +// +class hhEntitySpawner : public idEntity { +public: + CLASS_PROTOTYPE( hhEntitySpawner ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void Event_Activate(idEntity *activator); + void Event_SpawnEntity( void ); + + idStr entDefName; + idDict entSpawnArgs; + int maxSpawnCount; + int currSpawnCount; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_events.cpp b/neo/prey/Prey/game_events.cpp new file mode 100644 index 00000000..f86a0594 --- /dev/null +++ b/neo/prey/Prey/game_events.cpp @@ -0,0 +1,12 @@ +// game_events.cpp +// +// All of our events that have use to multiple classes are kept together so everyone can get at them + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + + + diff --git a/neo/prey/Prey/game_events.h b/neo/prey/Prey/game_events.h new file mode 100644 index 00000000..4768f5be --- /dev/null +++ b/neo/prey/Prey/game_events.h @@ -0,0 +1,18 @@ +#ifndef __PREY_EVENTS_H__ +#define __PREY_EVENTS_H__ + + +/* +extern const idEventDef EV_Kill; + +extern const idEventDef EV_Trigger; +extern const idEventDef EV_Retrigger; +extern const idEventDef EV_UnTrigger; +extern const idEventDef EV_PollForUntouch; + +extern const idEventDef EV_Hit; +extern const idEventDef EV_Stay; +extern const idEventDef EV_Double; +*/ + +#endif // __PREY_EVENTS_H__ diff --git a/neo/prey/Prey/game_fixedpod.cpp b/neo/prey/Prey/game_fixedpod.cpp new file mode 100644 index 00000000..466f0f79 --- /dev/null +++ b/neo/prey/Prey/game_fixedpod.cpp @@ -0,0 +1,209 @@ +// Game_FixedPod.cpp +// +// non-moving exploding pod + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_ExplodedBy("", "e"); +const idEventDef EV_SpawnShrapnel(""); + +CLASS_DECLARATION(idEntity, hhFixedPod) + EVENT( EV_Activate, hhFixedPod::Event_Trigger ) + EVENT( EV_ExplodeDamage, hhFixedPod::Event_ExplodeDamage ) + EVENT( EV_ExplodedBy, hhFixedPod::Event_ExplodedBy ) + EVENT( EV_SpawnShrapnel, hhFixedPod::Event_SpawnDebris ) + EVENT( EV_Broadcast_AssignFx, hhFixedPod::Event_AssignFx ) +END_CLASS + + +hhFixedPod::hhFixedPod() { + fx = NULL; +} + +//========================================================================== +// +// hhFixedPod::Spawn +// +//========================================================================== +void hhFixedPod::Spawn(void) { + + fl.takedamage = true; + + // setup the clipModel + GetPhysics()->SetContents( CONTENTS_SOLID ); + + // Spawn the energy beam + hhBeamSystem *beam = hhBeamSystem::SpawnBeam( GetOrigin(), spawnArgs.GetString("beam") ); + if( beam ) { + beam->SetOrigin(GetPhysics()->GetOrigin() - GetPhysics()->GetAxis()[2]*28); + beam->SetAxis(GetPhysics()->GetAxis()); + if (IsBound() || spawnArgs.GetBool("force_bind")) { + beam->Bind(this, false); // Only bind if we will be moving + } + beam->SetTargetLocation(beam->GetPhysics()->GetOrigin() + GetPhysics()->GetAxis()[2] * 56); + // Bound entities automatically removed upon destruction + } + + hhFxInfo fxInfo; + if (IsBound() || spawnArgs.GetBool("force_bind")) { + fxInfo.SetEntity( this ); // Only bind if we will be moving + } + fxInfo.RemoveWhenDone( false ); + fxInfo.NoRemoveWhenUnbound( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_energybeam"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx ); + + StartSound( "snd_idle", SND_CHANNEL_IDLE, 0, true, NULL ); +} + +//========================================================================== +// +// hhFixedPod::Event_AssignFxSmoke +// +//========================================================================== +void hhFixedPod::Event_AssignFx( hhEntityFx* fx ) { + this->fx = fx; +} + +void hhFixedPod::Save(idSaveGame *savefile) const { + fx.Save(savefile); +} + +void hhFixedPod::Restore( idRestoreGame *savefile ) { + fx.Restore(savefile); +} + +//========================================================================== +// +// hhFixedPod::Killed +// +//========================================================================== +void hhFixedPod::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; + + // Activate targets + ActivateTargets( attacker ); + + GetPhysics()->SetContents( 0 ); + + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS( &EV_ExplodedBy, 0, attacker ); +} + + +//========================================================================== +/// +// hhFixedPod::Explode +// +//========================================================================== +void hhFixedPod::Explode( idEntity *attacker ) { + hhFxInfo fxInfo; + + if (fx.IsValid()) { + fx->Hide(); + fx->PostEventMS(&EV_Remove, 0); + } + + // Spawn explosion + StopSound( SND_CHANNEL_IDLE, true ); + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, true, NULL ); + + fxInfo.SetNormal( idVec3(0.0f, 0.0f, 1.0f) ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", GetOrigin(), GetAxis(), &fxInfo ); + + Hide(); + + PostEventMS( &EV_SpawnShrapnel, 10 ); + PostEventMS( &EV_ExplodeDamage, 250, attacker ); // NOTE: This even MUST occur before the remove event + PostEventMS( &EV_Remove, 500 ); // Remove after a small delay to allow sound commands to execute +} + +//========================================================================== +// +// hhFixedPod::SpawnDebris +// +//========================================================================== +void hhFixedPod::SpawnDebris() { + idEntity* ent = NULL; + idVec3 launchDir; + int amount = 0; + idDebris* debris = NULL; + const idDict *dict = NULL; + + int numShrapnel = spawnArgs.GetInt( "debris_count" ); + if( !numShrapnel ) { + return; + } + + for( const idKeyValue* kv = spawnArgs.MatchPrefix("def_debris", NULL); kv; kv = spawnArgs.MatchPrefix("def_debris", kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + dict = gameLocal.FindEntityDefDict( kv->GetValue().c_str(), false ); + if( !dict ) { + continue; + } + + amount = hhMath::hhMax( 1, gameLocal.random.RandomInt(numShrapnel) ); + for ( int i = 0; i < amount; i++ ) { + launchDir = hhUtils::RandomVector(); + launchDir.Normalize(); + + gameLocal.SpawnEntityDef( *dict, &ent ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'%s' is not an idDebris", kv->GetValue().c_str() ); + } + + debris = static_cast(ent); + debris->Create( this, GetOrigin(), launchDir.ToMat3() ); + debris->Launch(); + } + } +} + +//========================================================================== +// +// hhFixedPod::Event_ExplodeDamage +// +// Applies the radius damage slightly after the actual explosion, so that +// when one explodes it will cascade and explode other fixed pods. +//========================================================================== + +void hhFixedPod::Event_ExplodeDamage( idEntity *attacker ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, spawnArgs.GetString("def_explodedamage") ); +} + +//========================================================================== +// +// hhFixedPod::Event_ExplodedBy +// +//========================================================================== +void hhFixedPod::Event_ExplodedBy( idEntity *activator ) { + Explode(activator); +} + +//========================================================================== +// +// hhFixedPod::Event_Trigger +// +//========================================================================== + +void hhFixedPod::Event_Trigger( idEntity *activator ) { + Explode(activator); +} + +//========================================================================== +// +// hhFixedPod::Event_SpawnDebris +// +//========================================================================== + +void hhFixedPod::Event_SpawnDebris() { + SpawnDebris(); +} + diff --git a/neo/prey/Prey/game_fixedpod.h b/neo/prey/Prey/game_fixedpod.h new file mode 100644 index 00000000..00719462 --- /dev/null +++ b/neo/prey/Prey/game_fixedpod.h @@ -0,0 +1,31 @@ + +#ifndef __GAME_FIXEDPOD_H__ +#define __GAME_FIXEDPOD_H__ + +extern const idEventDef EV_ExplodedBy; + +class hhFixedPod : public idEntity { +public: + CLASS_PROTOTYPE( hhFixedPod ); + + hhFixedPod(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Explode( idEntity *attacker ); + void SpawnDebris(); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_ExplodeDamage( idEntity *attacker ); + void Event_ExplodedBy( idEntity *activator ); + void Event_SpawnDebris(); + void Event_AssignFx( hhEntityFx* fx ); + + idEntityPtr fx; +}; + + +#endif /* __GAME_FIXEDPOD_H__ */ diff --git a/neo/prey/Prey/game_forcefield.cpp b/neo/prey/Prey/game_forcefield.cpp new file mode 100644 index 00000000..7588d758 --- /dev/null +++ b/neo/prey/Prey/game_forcefield.cpp @@ -0,0 +1,625 @@ +//************************************************************************** +//** +//** GAME_FORCEFIELD.CPP +//** +//** Game code for the forcefield +//** +//************************************************************************** + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idEntity, hhForceField ) + EVENT( EV_Activate, hhForceField::Event_Activate ) +END_CLASS + +/* +=========== +hhForceField::Spawn +=========== +*/ +void hhForceField::Spawn(void) { + fl.takedamage = true; + SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); + SetShaderParm( SHADERPARM_MODE, 0.0f ); + + fade = 0.0f; + + activationRate = spawnArgs.GetFloat( "activationRate" ); + deactivationRate = spawnArgs.GetFloat( "deactivationRate" ); + undamageFadeRate = spawnArgs.GetFloat( "undamageFadeRate" ); + +// BecomeActive( TH_THINK|TH_TICKER ); + + cachedContents = CONTENTS_FORCEFIELD | CONTENTS_BLOCK_RADIUSDAMAGE | CONTENTS_SHOOTABLE; + + physicsObj.SetSelf( this ); + + if (spawnArgs.GetBool("isSimpleBox")) { + // Simple boxes are cheaper and can be bound to other objects + physicsObj.SetClipModel( new idClipModel(idTraceModel(GetPhysics()->GetBounds())), 1.0f ); + physicsObj.SetContents( cachedContents ); + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + SetPhysics( &physicsObj ); + } + else { + // Non-simple has real per-poly collision with it's model, uses default static physics because we don't have a tracemodel. + // This loses the activation of contacts on the object (so some things may not fall through when disabled), but in my tests, + // the spirit proxy falls through fine. NOTE: To fix the movables, etc. not falling when these are turned off, I'm manually + // Activating appropriate physics that overlap the bounds when Turned off. +// physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); +// physicsObj.SetContents( cachedContents ); +// physicsObj.SetOrigin( GetOrigin() ); +// physicsObj.SetAxis( GetAxis() ); +// SetPhysics( &physicsObj ); + + // Apparently the flags need to be on the material to make projectiles hit these. + GetPhysics()->SetContents(cachedContents); + } + + EnterOnState(); + damagedState = false; + + nextCollideFxTime = gameLocal.time; + + + fl.networkSync = true; //always want to sync over the net +} + +void hhForceField::Save( idSaveGame *savefile ) const { + savefile->WriteInt( fieldState ); + savefile->WriteBool( damagedState ); + savefile->WriteFloat( activationRate ); + savefile->WriteFloat( deactivationRate ); + savefile->WriteFloat( undamageFadeRate ); + savefile->WriteInt( applyImpulseAttempts ); + savefile->WriteInt( cachedContents ); + savefile->WriteFloat( fade ); + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteStaticObject( physicsObj ); +} + +void hhForceField::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( reinterpret_cast ( fieldState ) ); + savefile->ReadBool( damagedState ); + savefile->ReadFloat( activationRate ); + savefile->ReadFloat( deactivationRate ); + savefile->ReadFloat( undamageFadeRate ); + savefile->ReadInt( applyImpulseAttempts ); + savefile->ReadInt( cachedContents ); + savefile->ReadFloat( fade ); + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadStaticObject( physicsObj ); + + // Only restore physics if we were using it before + if (spawnArgs.GetBool("isSimpleBox")) { + RestorePhysics( &physicsObj ); + } +} + +void hhForceField::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot(msg); + msg.WriteBits(damagedState, 1); + msg.WriteFloat(activationRate); + msg.WriteFloat(deactivationRate); + msg.WriteFloat(undamageFadeRate); + msg.WriteBits(applyImpulseAttempts, 32); + msg.WriteBits(cachedContents, 32); + msg.WriteFloat(fade); + msg.WriteBits(nextCollideFxTime, 32); + msg.WriteBits(fieldState, 4); + msg.WriteBits(IsHidden(), 1); + + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); +} + +void hhForceField::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot(msg); + damagedState = !!msg.ReadBits(1); + activationRate = msg.ReadFloat(); + deactivationRate = msg.ReadFloat(); + undamageFadeRate = msg.ReadFloat(); + applyImpulseAttempts = msg.ReadBits(32); + cachedContents = msg.ReadBits(32); + fade = msg.ReadFloat(); + nextCollideFxTime = msg.ReadBits(32); + fieldState = (States)msg.ReadBits(4); + bool hidden = !!msg.ReadBits(1); + if (IsHidden() != hidden) { + if (hidden) { + Hide(); + SetSkinByName( spawnArgs.GetString("skin_off" ) ); + GetPhysics()->SetContents( 0 ); + } + else { + Show(); + SetSkinByName( NULL ); + GetPhysics()->SetContents( cachedContents ); + } + } + + float f; + bool changed = false; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] != f)); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = f; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_MODE] != f)); + renderEntity.shaderParms[SHADERPARM_MODE] = f; + + if (changed) { + UpdateModel(); + UpdateVisuals(); + } +} + +void hhForceField::ClientPredictionThink( void ) { + idEntity::ClientPredictionThink(); +} + +/* +=========== +hhForceField::ApplyImpulse +=========== +*/ +void hhForceField::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + + if (!ent->IsType(idActor::Type)) { + // Don't play sound for actor footsteps, landings. They play their own localized sounds for + // the sake of very large forcefields. + StartSound( "snd_impulse", SND_CHANNEL_ANY ); + } + EnterDamagedState(); + + // Apply the hit effect + trace_t trace; + idStr fxCollide = spawnArgs.GetString( "fx_collide" ); + + if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime && point != GetOrigin()) { + // Trace to find normal + idVec3 dir = force; + dir.NormalizeFast(); + dir *= 200; + idVec3 start = point-dir; + idVec3 end = point+dir; + memset(&trace, 0, sizeof(trace)); + gameLocal.clip.TracePoint(trace, start, end, CONTENTS_FORCEFIELD, ent); + + if (trace.fraction < 1.0f) { + // Spawn fx oriented to normal of collision + hhFxInfo fxInfo; + fxInfo.SetNormal( trace.c.normal ); + BroadcastFxInfo( fxCollide, trace.c.point, mat3_identity, &fxInfo, 0, false ); //rww - changed to not broadcast + nextCollideFxTime = gameLocal.time + 200; + } + } +} + +/* +=========== +hhForceField::Ticker +=========== +*/ +void hhForceField::Ticker( void ) { + + if( fieldState == StatePreTurningOn ) { + idEntity* entityList[MAX_GENTITIES]; + idEntity* entity = NULL; + idPhysics* physics = NULL; + int numEntities = 0; + int numEntitiesImpulseAppliedTo = 0; + idVec3 force = DetermineForce() * GetAxis(); + + numEntities = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SOLID, entityList, MAX_GENTITIES ); + for( int ix = 0; ix < numEntities; ++ix ) { + entity = entityList[ix]; + + if( !entity ) { + continue; + } + + if( entity == this ) { + continue; + } + + //Removing mass from any calculations regarding impulse + entity->ApplyImpulse( this, 0, entity->GetOrigin(), force * entity->GetPhysics()->GetMass() ); + numEntitiesImpulseAppliedTo++; + } + + --applyImpulseAttempts; + if( !numEntitiesImpulseAppliedTo || !applyImpulseAttempts ) { + fieldState = StateTurningOn; + GetPhysics()->SetContents( cachedContents ); + SetSkin( NULL ); + StartSound( "snd_start", SND_CHANNEL_ANY ); + + Show(); + + EnterTurningOnState(); + } + } else if( fieldState == StateTurningOn ) { + float deltaTime = MS2SEC( gameLocal.msec ); + + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] += activationRate * deltaTime; + if( renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] >= 1.0f ) { + EnterOnState(); + } + + UpdateVisuals(); + } else if( fieldState == StateTurningOff ) { + float deltaTime = MS2SEC( gameLocal.msec ); + + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] -= deactivationRate * deltaTime; + if( renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] <= 0.0f ) { + EnterOffState(); + } + + UpdateVisuals(); + } + + if( damagedState ) { + float deltaTime = MS2SEC( gameLocal.msec ); + + // Fade parm back to normal + fade -= undamageFadeRate * deltaTime; + if ( fade <= 0.0f ) { // Finished fading + fade = 0; + damagedState = false; + } + + SetShaderParm( SHADERPARM_MODE, fade ); + } + + if (!damagedState && (fieldState==StateOn || fieldState==StateOff)) { + BecomeInactive( TH_TICKER ); + } +} + +/* +=========== +hhForceField::DetermineThinnestAxis +=========== +*/ +int hhForceField::DetermineThinnestAxis() { + int best = 0; + idBounds bounds = GetPhysics()->GetBounds(); + for( int i = 1; i < 3; ++i ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + + return best; +} + +/* +=========== +hhForceField::DetermineForce +=========== +*/ +idVec3 hhForceField::DetermineForce() { + idVec3 force( vec3_origin ); + float forceMagnitude = spawnArgs.GetFloat( "forceMagnitude" ); + int dir = spawnArgs.GetBool( "positiveDir", "1" ) ? 1.0f : -1.0f; + + force[DetermineThinnestAxis()] = forceMagnitude * dir; + + return force; +} + +/* +=========== +hhForceField::IsAtRest + +Used to activate entites at rest +=========== +*/ +bool hhForceField::IsAtRest( int id ) const { + return ( fieldState != StateOff ); +} + +/* +=========== +hhForceField::Event_Activate +=========== +*/ +void hhForceField::Event_Activate( idEntity *activator ) { + switch( fieldState ) { + case StatePreTurningOn: + EnterTurningOffState(); + break; + case StateTurningOff: + EnterPreTurningOnState(); + break; + case StateTurningOn: + EnterTurningOffState(); + break; + case StateOn: + EnterTurningOffState(); + break; + case StateOff: + EnterPreTurningOnState(); + break; + } +} + +/* +=========== +hhForceField::EnterDamagedState +=========== +*/ +void hhForceField::EnterDamagedState() { + damagedState = true; + fade = 1.0f; + SetShaderParm( SHADERPARM_MODE, fade ); // Instantly change to the new shader + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterPreTurningOnState +=========== +*/ +void hhForceField::EnterPreTurningOnState() { + fieldState = StatePreTurningOn; + applyImpulseAttempts = spawnArgs.GetInt( "applyImpulseAttempts" ); + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterOnState +=========== +*/ +void hhForceField::EnterOnState() { + fieldState = StateOn; + StartSound( "snd_loop", SND_CHANNEL_IDLE ); + //HUMANHEAD PCF rww 05/15/06 - prevent stuck-in-forcefield by killing everything within + if (gameLocal.isMultiplayer) { + gameLocal.KillBoxMasked( this, CONTENTS_BODY ); + } + //HUMANHEAD END +} + +/* +=========== +hhForceField::EnterTurningOnState +=========== +*/ +void hhForceField::EnterTurningOnState() { + GetPhysics()->SetContents( cachedContents ); + SetSkin( NULL ); + StartSound( "snd_start", SND_CHANNEL_ANY ); + + Show(); + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterTurningOffState +=========== +*/ +void hhForceField::EnterTurningOffState() { + fieldState = StateTurningOff; + StopSound( SND_CHANNEL_IDLE ); + StartSound( "snd_stop", SND_CHANNEL_ANY ); + BecomeActive( TH_TICKER ); +} + +/* +=========== +hhForceField::EnterOffState +=========== +*/ +void hhForceField::EnterOffState() { + fieldState = StateOff; + if (spawnArgs.GetBool("isSimpleBox")) { + GetPhysics()->ActivateContactEntities(); + } + else { + // To allow complex shaped forcefields, we need to manually activate the physics of any contacts here + idClipModel *clipModels[ MAX_GENTITIES ]; + idClipModel *cm; + idBounds bounds = GetPhysics()->GetAbsBounds(); + bounds.ExpandSelf(bounds.GetRadius()*0.1f); // Expand the bounds by 10% to catch things on the surface + int num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_ALL, clipModels, MAX_GENTITIES ); + for ( int i=0;iGetEntity(); + if (hit && hit->GetPhysics()->IsAtRest() && + (hit->GetPhysics()->IsType(idPhysics_RigidBody::Type) || hit->GetPhysics()->IsType(idPhysics_AF::Type)) ) { + if ( !hit->IsType( hhVehicle::Type ) ) { //HUMANHEAD jsh PCF 5/26/06: fix 30hz vehicle console jittering + hit->GetPhysics()->Activate(); + } + } + } + } + + // HUMANHEAD PCF pdm 04/27/06: Unbind any bound projectiles + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); // bjk drops all bound projectiles such as arrows and mines + next = teamChain; + + //HUMANHEAD bjk PCF (4-28-06) - explode crawlers + if (ent->IsType(hhProjectileStickyCrawlerGrenade::Type)) { + static_cast(ent)->PostEventSec( &EV_Explode, 0.2f ); + } + } + } + + GetPhysics()->SetContents( 0 ); + Hide(); + SetSkinByName( spawnArgs.GetString("skin_off" ) ); // Set the force field to completely not draw +} + +//-------------------------------------------------------------------------- +// hhShuttleForcefield +//-------------------------------------------------------------------------- +CLASS_DECLARATION( idEntity, hhShuttleForceField ) + EVENT( EV_Activate, hhShuttleForceField::Event_Activate ) +END_CLASS + +void hhShuttleForceField::Spawn() { + nextCollideFxTime = gameLocal.time; + GetPhysics()->SetContents(CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + + // Start in the on state + fieldState = StateOn; + fade.Init(gameLocal.time, 0, 1.0f, 1.0f); + SetShaderParm(SHADERPARM_TIMEOFFSET, fade.GetCurrentValue(gameLocal.time)); + + fl.networkSync = true; //rww +} + +void hhShuttleForceField::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + trace_t trace; + idStr fxCollide = spawnArgs.GetString( "fx_collide" ); + + if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime && point != GetOrigin()) { + + // Trace to find normal + idVec3 dir = force; + dir.NormalizeFast(); + dir *= 200; + idVec3 start = point-dir; + idVec3 end = point+dir; + memset(&trace, 0, sizeof(trace)); + gameLocal.clip.TracePoint(trace, start, end, CONTENTS_SOLID, ent); + + if (trace.fraction < 1.0f) { + // Spawn fx oriented to normal of collision + hhFxInfo fxInfo; + fxInfo.SetNormal( trace.c.normal ); + BroadcastFxInfo( fxCollide, trace.c.point, mat3_identity, &fxInfo ); + nextCollideFxTime = gameLocal.time + 200; + + StartSound( "snd_collide", SND_CHANNEL_ANY, 0, true, NULL ); + ActivatePrefixed( "triggerCollide", this ); // bg: Feedback hook. + } + } +} + +void hhShuttleForceField::Ticker() { + SetShaderParm(SHADERPARM_TIMEOFFSET, fade.GetCurrentValue(gameLocal.time)); + if (fade.IsDone(gameLocal.time)) { + if (fieldState == StateTurningOn) { + // Entering On state + fieldState = StateOn; + GetPhysics()->SetContents(CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + } + else if (fieldState == StateTurningOff) { + // Entering Off state + fieldState = StateOff; + GetPhysics()->SetContents(0); + } + + BecomeInactive(TH_TICKER); + } +} + +void hhShuttleForceField::Event_Activate(idEntity *activator) { + int duration = SEC2MS(spawnArgs.GetFloat("fade_duration")); + float currentFade = fade.GetCurrentValue(gameLocal.time); + if (fieldState == StateOn || fieldState == StateTurningOn) { + // Entering TurningOff state + fade.Init(gameLocal.time, duration, currentFade, 0.0f); + fieldState = StateTurningOff; + } + else if (fieldState == StateOff || fieldState == StateTurningOff) { + // Entering TurningOn state + fade.Init(gameLocal.time, duration, currentFade, 1.0f); + fieldState = StateTurningOn; + //HUMANHEAD PCF rww 05/15/06 - prevent stuck-in-forcefield by killing everything within + if (gameLocal.isMultiplayer) { + gameLocal.KillBoxMasked( this, CONTENTS_BODY ); + } + //HUMANHEAD END + } + BecomeActive( TH_TICKER ); +} + +void hhShuttleForceField::Save( idSaveGame *savefile ) const { + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteInt( fieldState ); + savefile->WriteFloat( fade.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fade.GetDuration() ); + savefile->WriteFloat( fade.GetStartValue() ); + savefile->WriteFloat( fade.GetEndValue() ); +} + +void hhShuttleForceField::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadInt( reinterpret_cast ( fieldState ) ); + + savefile->ReadFloat( set ); // idInterpolate + fade.SetStartTime( set ); + savefile->ReadFloat( set ); + fade.SetDuration( set ); + savefile->ReadFloat( set ); + fade.SetStartValue(set); + savefile->ReadFloat( set ); + fade.SetEndValue( set ); +} + +void hhShuttleForceField::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(GetPhysics()->GetContents(), 32); + + msg.WriteBits(nextCollideFxTime, 32); + msg.WriteBits(fieldState, 8); + + msg.WriteFloat(fade.GetStartTime()); + msg.WriteFloat(fade.GetDuration()); + msg.WriteFloat(fade.GetStartValue()); + msg.WriteFloat(fade.GetEndValue()); + + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); +} + +void hhShuttleForceField::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int contents = msg.ReadBits(32); + if (contents != GetPhysics()->GetContents()) { + GetPhysics()->SetContents(contents); + } + + nextCollideFxTime = msg.ReadBits(32); + fieldState = (States)msg.ReadBits(8); + + fade.SetStartTime(msg.ReadFloat()); + fade.SetDuration(msg.ReadFloat()); + fade.SetStartValue(msg.ReadFloat()); + fade.SetEndValue(msg.ReadFloat()); + + float f; + bool changed = false; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] != f)); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = f; + + f = msg.ReadFloat(); + changed = (changed || (renderEntity.shaderParms[SHADERPARM_MODE] != f)); + renderEntity.shaderParms[SHADERPARM_MODE] = f; + + if (changed) { + UpdateVisuals(); + } +} + +void hhShuttleForceField::ClientPredictionThink( void ) { + idEntity::ClientPredictionThink(); +} diff --git a/neo/prey/Prey/game_forcefield.h b/neo/prey/Prey/game_forcefield.h new file mode 100644 index 00000000..d587d1ee --- /dev/null +++ b/neo/prey/Prey/game_forcefield.h @@ -0,0 +1,94 @@ +#ifndef __GAME_FORCEFIELD_H__ +#define __GAME_FORCEFIELD_H__ + +class hhForceField : public idEntity { + CLASS_PROTOTYPE( hhForceField ); + +public: + void Spawn(); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + void Ticker(); + + int DetermineThinnestAxis(); + idVec3 DetermineForce(); + + virtual bool IsAtRest( int id ) const; + + void EnterDamagedState(); + void EnterPreTurningOnState(); + void EnterTurningOnState(); + void EnterOnState(); + void EnterTurningOffState(); + void EnterOffState(); + +protected: + void Event_Activate( idEntity *activator ); + +protected: + enum States { + StatePreTurningOn = 0, + StateTurningOn, + StateOn, + StateTurningOff, + StateOff + } fieldState; + bool damagedState; + + float activationRate; + float deactivationRate; + float undamageFadeRate; + + int applyImpulseAttempts; + + int cachedContents; + + float fade; + + int nextCollideFxTime; + + hhPhysics_StaticForceField physicsObj; +}; + + +class hhShuttleForceField : public idEntity { + CLASS_PROTOTYPE( hhShuttleForceField ); + +public: + void Spawn(); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void Ticker(); + +protected: + void Event_Activate(idEntity *activator); + + enum States { + StatePreTurningOn = 0, + StateTurningOn, + StateOn, + StateTurningOff, + StateOff + } fieldState; + +private: + int nextCollideFxTime; + idInterpolate fade; +}; + +#endif /* __GAME_FORCEFIELD_H__ */ diff --git a/neo/prey/Prey/game_fxinfo.cpp b/neo/prey/Prey/game_fxinfo.cpp new file mode 100644 index 00000000..61207be2 --- /dev/null +++ b/neo/prey/Prey/game_fxinfo.cpp @@ -0,0 +1,343 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( idClass, hhFxInfo ) +END_CLASS + + +hhFxInfo::hhFxInfo() { + Reset(); +} + +hhFxInfo::hhFxInfo( const hhFxInfo* fxInfo ) { + Assign( fxInfo ); +} + +hhFxInfo::hhFxInfo( const hhFxInfo& fxInfo ) { + Assign( &fxInfo ); +} + +void hhFxInfo::Save(idSaveGame *savefile) const { + fxInfoFlags_s infoFlags = flags; + LittleBitField( &infoFlags, sizeof( infoFlags ) ); + savefile->Write( &infoFlags, sizeof( infoFlags ) ); + + savefile->WriteVec3( normal ); + savefile->WriteVec3( incomingVector ); + savefile->WriteVec3( bounceVector ); + savefile->WriteString( bindBone ); + entity.Save(savefile); +} + +void hhFxInfo::Restore( idRestoreGame *savefile ) { + savefile->Read( &flags, sizeof( flags ) ); + LittleBitField( &flags, sizeof( flags ) ); + + savefile->ReadVec3( normal ); + savefile->ReadVec3( incomingVector ); + savefile->ReadVec3( bounceVector ); + savefile->ReadString( bindBone ); + entity.Restore(savefile); +} + +void hhFxInfo::WriteToSnapshot( idBitMsgDelta &msg ) const { + /* + msg.WriteBits(flags.normalIsSet, 1); + msg.WriteBits(flags.incomingIsSet, 1); + msg.WriteBits(flags.bounceIsSet, 1); + msg.WriteBits(flags.start, 1); + msg.WriteBits(flags.removeWhenDone, 1); + msg.WriteBits(flags.toggle, 1); + msg.WriteBits(flags.onlyVisibleInSpirit, 1); + msg.WriteBits(flags.onlyInvisibleInSpirit, 1); + msg.WriteBits(flags.useWeaponDepthHack, 1); + msg.WriteBits(flags.bNoRemoveWhenUnbound, 1); + + msg.WriteFloat(normal.x); + msg.WriteFloat(normal.y); + msg.WriteFloat(normal.z); + msg.WriteFloat(incomingVector.x); + msg.WriteFloat(incomingVector.y); + msg.WriteFloat(incomingVector.z); + msg.WriteFloat(bounceVector.x); + msg.WriteFloat(bounceVector.y); + msg.WriteFloat(bounceVector.z); + msg.WriteString(bindBone); + msg.WriteBits(entity.GetSpawnId(), 32); + */ + + msg.WriteBits(flags.normalIsSet, 1); + msg.WriteBits(flags.removeWhenDone, 1); + msg.WriteFloat(normal.x); + msg.WriteFloat(normal.y); + msg.WriteFloat(normal.z); +} + +void hhFxInfo::ReadFromSnapshot( const idBitMsgDelta &msg ) { + /* + flags.normalIsSet = !!msg.ReadBits(1); + flags.incomingIsSet = !!msg.ReadBits(1); + flags.bounceIsSet = !!msg.ReadBits(1); + flags.start = !!msg.ReadBits(1); + flags.removeWhenDone = !!msg.ReadBits(1); + flags.toggle = !!msg.ReadBits(1); + flags.onlyVisibleInSpirit = !!msg.ReadBits(1); + flags.onlyInvisibleInSpirit = !!msg.ReadBits(1); + flags.useWeaponDepthHack = !!msg.ReadBits(1); + flags.bNoRemoveWhenUnbound = !!msg.ReadBits(1); + + normal.x = msg.ReadFloat(); + normal.y = msg.ReadFloat(); + normal.z = msg.ReadFloat(); + incomingVector.x = msg.ReadFloat(); + incomingVector.y = msg.ReadFloat(); + incomingVector.z = msg.ReadFloat(); + bounceVector.x = msg.ReadFloat(); + bounceVector.y = msg.ReadFloat(); + bounceVector.z = msg.ReadFloat(); + + char buf[128]; + msg.ReadString(buf, 128); + bindBone = buf; + entity.SetSpawnId(msg.ReadBits(32)); + */ + + flags.normalIsSet = !!msg.ReadBits(1); + flags.removeWhenDone = !!msg.ReadBits(1); + normal.x = msg.ReadFloat(); + normal.y = msg.ReadFloat(); + normal.z = msg.ReadFloat(); +} + +hhFxInfo& hhFxInfo::Assign( const hhFxInfo* fxInfo ) { + Reset(); + + if( !fxInfo ) { + return *this; + } + + if( fxInfo->NormalIsSet() ) { + SetNormal( fxInfo->GetNormal() ); + } + + if( fxInfo->IncomingVectorIsSet() ) { + SetIncomingVector( fxInfo->GetIncomingVector() ); + } + + if( fxInfo->BounceVectorIsSet() ) { + SetBounceVector( fxInfo->GetBounceVector() ); + } + + if( fxInfo->EntityIsSet() ) { + SetEntity( fxInfo->GetEntity() ); + } + + if( fxInfo->BindBoneIsSet() ) { + SetBindBone( fxInfo->GetBindBone() ); + } + + SetStart( fxInfo->StartIsSet() ); + + RemoveWhenDone( fxInfo->RemoveWhenDone() ); + + Toggle( fxInfo->Toggle() ); + + OnlyVisibleInSpirit( fxInfo->OnlyVisibleInSpirit() ); + OnlyInvisibleInSpirit( fxInfo->OnlyInvisibleInSpirit() ); + UseWeaponDepthHack( fxInfo->UseWeaponDepthHack() ); + NoRemoveWhenUnbound( fxInfo->NoRemoveWhenUnbound() ); + + return *this; +} + +hhFxInfo& hhFxInfo::operator=( const hhFxInfo* fxInfo ) { + return Assign( fxInfo ); +} + +hhFxInfo& hhFxInfo::operator=( const hhFxInfo& fxInfo ) { + return Assign( &fxInfo ); +} + +void hhFxInfo::SetNormal( const idVec3 &v ) { + assert( v.Length() ); + normal = v; + flags.normalIsSet = true; +} + +void hhFxInfo::SetIncomingVector( const idVec3 &v ) { + assert( v.Length() ); + incomingVector = v; + flags.incomingIsSet = true; +} + +void hhFxInfo::SetBounceVector( const idVec3 &v ) { + assert( v.Length() ); + bounceVector = v; + flags.bounceIsSet = true; +} + +void hhFxInfo::SetEntity( idEntity *e ) { + entity = e; +} + +void hhFxInfo::SetBindBone( const char* bindBone ) { + this->bindBone = bindBone; +} + +void hhFxInfo::SetStart(const bool start) { + flags.start = start; +} + +void hhFxInfo::RemoveWhenDone( const bool removeWhenDone ) { + flags.removeWhenDone = removeWhenDone; +} + +void hhFxInfo::Toggle( const bool tf ) { + flags.toggle = tf; +} + +void hhFxInfo::OnlyVisibleInSpirit( const bool spirit ) { + flags.onlyVisibleInSpirit = spirit; +} + +void hhFxInfo::OnlyInvisibleInSpirit( const bool spirit ) { + flags.onlyInvisibleInSpirit = spirit; +} + +void hhFxInfo::UseWeaponDepthHack( const bool weaponDepthHack ) { + flags.useWeaponDepthHack = weaponDepthHack; +} + +void hhFxInfo::Triggered( const bool tf ) { + flags.triggered = tf; +} + +const idVec3& hhFxInfo::GetNormal( ) const { + assert( flags.normalIsSet ); + return( normal ); +} + +const idVec3& hhFxInfo::GetIncomingVector( ) const { + assert( flags.incomingIsSet ); + return( incomingVector ); +} + +const idVec3& hhFxInfo::GetBounceVector( ) const { + assert( flags.bounceIsSet ); + return( bounceVector ); +} + +const char* hhFxInfo::GetBindBone() const { + return bindBone.c_str(); +} + +idEntity* const hhFxInfo::GetEntity( ) const { + return( entity.GetEntity() ); +} + +bool hhFxInfo::RemoveWhenDone() const { + return flags.removeWhenDone; +} + +bool hhFxInfo::StartIsSet() const { + return flags.start; +} + +bool hhFxInfo::NormalIsSet( ) const { + return flags.normalIsSet; +} + +bool hhFxInfo::IncomingVectorIsSet( ) const { + return flags.incomingIsSet; +} + +bool hhFxInfo::BounceVectorIsSet( ) const { + return flags.bounceIsSet; +} + +bool hhFxInfo::BindBoneIsSet() const { + return bindBone.Length() > 0; +} + +bool hhFxInfo::EntityIsSet() const { + return entity.IsValid(); +} + +bool hhFxInfo::Toggle() const { + return flags.toggle; +} + +bool hhFxInfo::OnlyVisibleInSpirit( void ) const { + return flags.onlyVisibleInSpirit; +} + +bool hhFxInfo::OnlyInvisibleInSpirit( void ) const { + return flags.onlyInvisibleInSpirit; +} + +bool hhFxInfo::UseWeaponDepthHack() const { + return flags.useWeaponDepthHack; +} + +bool hhFxInfo::Triggered() const { + return flags.triggered; +} + +void hhFxInfo::NoRemoveWhenUnbound( const bool noRemove ) { + flags.bNoRemoveWhenUnbound = noRemove; +} + +bool hhFxInfo::NoRemoveWhenUnbound() const { + return flags.bNoRemoveWhenUnbound; +} + +void hhFxInfo::Reset() { + entity = NULL; + normal.Zero(); bounceVector.Zero(); + incomingVector.Zero(); + bindBone.Empty(); + flags.normalIsSet = false; + flags.bounceIsSet = false; + flags.incomingIsSet = false; + flags.start = true; + flags.removeWhenDone = true; + flags.toggle = false; // jrm + flags.onlyVisibleInSpirit = false; + flags.onlyInvisibleInSpirit = false; // tmj + flags.useWeaponDepthHack = false; + flags.triggered = false; // mdl + flags.bNoRemoveWhenUnbound = false; // mdc +} + +bool hhFxInfo::GetAxisFor( int which, idVec3& dir ) const { + switch ( which ) { + case AXIS_NORMAL: + if ( NormalIsSet() ) { dir = GetNormal(); return true; } + else { + if ( g_debugFX.GetBool() ) gameLocal.Warning("Tried to get fxInfo Normal when it isn't set"); + } + break; + case AXIS_BOUNCE: + if ( BounceVectorIsSet() ) { dir = GetBounceVector(); return true; } + else { + if ( g_debugFX.GetBool() ) gameLocal.Warning("Tried to get fxInfo Bounce Vector when it isn't set"); + } + break; + case AXIS_CUSTOMLOCAL: + // handled in alternate path + if ( g_debugFX.GetBool() ) { + gameLocal.Warning("Using customlocal without axis"); + } + break; + case AXIS_INCOMING: + if ( IncomingVectorIsSet() ) { dir = GetIncomingVector(); return true; } + else { + if ( g_debugFX.GetBool() ) gameLocal.Warning("Tried to get fxInfo Incoming Vector when it isn't set"); + } + break; + } + return false; +} \ No newline at end of file diff --git a/neo/prey/Prey/game_fxinfo.h b/neo/prey/Prey/game_fxinfo.h new file mode 100644 index 00000000..1198209f --- /dev/null +++ b/neo/prey/Prey/game_fxinfo.h @@ -0,0 +1,175 @@ +#ifndef __HH_FX_INFO_H +#define __HH_FX_INFO_H + +class hhFxInfo : public idClass { + CLASS_PROTOTYPE( hhFxInfo ); + +public: + hhFxInfo(); + hhFxInfo( const hhFxInfo* fxInfo ); + hhFxInfo( const hhFxInfo& fxInfo ); + hhFxInfo& Assign( const hhFxInfo* fxInfo ); + hhFxInfo& operator=( const hhFxInfo* fxInfo ); + hhFxInfo& operator=( const hhFxInfo& fxInfo ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //HUMANHEAD rww + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + //HUMANHEAD END + + void SetNormal( const idVec3 &v ); + void SetIncomingVector( const idVec3 &v ); + void SetBounceVector( const idVec3 &v ); + void SetEntity( idEntity *e ); + void SetBindBone( const char* bindBone ); + void SetStart( const bool start ); + void RemoveWhenDone( const bool removeWhenDone ); + void Toggle( const bool tf ); + void OnlyVisibleInSpirit( const bool spirit ); + void OnlyInvisibleInSpirit( const bool spirit ); + void UseWeaponDepthHack( const bool weaponDepthHack ); + void Triggered( const bool tf ); + + void NoRemoveWhenUnbound( const bool noRemove ); + + const idVec3& GetNormal( ) const; + const idVec3& GetIncomingVector( ) const; + const idVec3& GetBounceVector( ) const; + const char* GetBindBone() const; + idEntity* const GetEntity( ) const; + + bool RemoveWhenDone() const; + bool StartIsSet() const; + bool NormalIsSet( ) const; + bool IncomingVectorIsSet( ) const; + bool BounceVectorIsSet( ) const; + bool BindBoneIsSet() const; + bool EntityIsSet() const; + bool Toggle() const; + bool OnlyVisibleInSpirit( void ) const; + bool OnlyInvisibleInSpirit( void ) const; + bool UseWeaponDepthHack() const; + bool Triggered() const; + + bool NoRemoveWhenUnbound() const; + + void Reset(); + + bool GetAxisFor( int which, idVec3& dir ) const; + + void WriteToBitMsg( idBitMsg* msg ) const; + void ReadFromBitMsg( const idBitMsg* bitMsg ); + +protected: + struct fxInfoFlags_s { + bool normalIsSet; + bool incomingIsSet; + bool bounceIsSet; + bool start; + bool removeWhenDone; + bool toggle; + bool onlyVisibleInSpirit; // CJR + bool onlyInvisibleInSpirit; // tmj + bool useWeaponDepthHack; + bool bNoRemoveWhenUnbound; //mdc + bool triggered; // mdl + } flags; + + idVec3 normal; + idVec3 incomingVector; + idVec3 bounceVector; + idStr bindBone; + idEntityPtr entity; +}; + +ID_INLINE void hhFxInfo::WriteToBitMsg( idBitMsg* msg ) const { + bool varIsSet = false; + + varIsSet = NormalIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteDir( GetNormal(), 24 ); + } + + varIsSet = IncomingVectorIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteDir( GetIncomingVector(), 24 ); + } + + varIsSet = BounceVectorIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteVec3( GetBounceVector() ); + } + + varIsSet = EntityIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteBits( GetEntity()->entityNumber, GENTITYNUM_BITS ); + } + + varIsSet = BindBoneIsSet(); + msg->WriteBool( varIsSet ); + if( varIsSet ) { + msg->WriteString( GetBindBone() ); + } + + msg->WriteBool( StartIsSet() ); + + msg->WriteBool( RemoveWhenDone() ); + + msg->WriteBool( Toggle() ); + + msg->WriteBool( OnlyVisibleInSpirit() ); + msg->WriteBool( OnlyInvisibleInSpirit() ); + msg->WriteBool( UseWeaponDepthHack() ); + msg->WriteBool( NoRemoveWhenUnbound() ); +} + +ID_INLINE void hhFxInfo::ReadFromBitMsg( const idBitMsg* msg ) { + bool varIsSet = false; + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetNormal( msg->ReadDir(24) ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetIncomingVector( msg->ReadDir(24) ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetBounceVector( msg->ReadVec3() ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + SetEntity( gameLocal.entities[ msg->ReadBits(GENTITYNUM_BITS) ] ); + } + + varIsSet = msg->ReadBool(); + if( varIsSet ) { + char boneName[256]; + msg->ReadString( boneName, 256 ); + SetBindBone( boneName ); + } + + SetStart( msg->ReadBool() ); + + RemoveWhenDone( msg->ReadBool() ); + + Toggle( msg->ReadBool() ); + + OnlyVisibleInSpirit( msg->ReadBool() ); + OnlyInvisibleInSpirit( msg->ReadBool() ); + UseWeaponDepthHack( msg->ReadBool() ); + NoRemoveWhenUnbound( msg->ReadBool() ); +} + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_gibbable.cpp b/neo/prey/Prey/game_gibbable.cpp new file mode 100644 index 00000000..dafb411d --- /dev/null +++ b/neo/prey/Prey/game_gibbable.cpp @@ -0,0 +1,146 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/********************************************************************** + +hhGibbable + +**********************************************************************/ + +const idEventDef EV_Respawn(""); + +CLASS_DECLARATION( hhAnimatedEntity, hhGibbable ) + EVENT( EV_Activate, hhGibbable::Event_Activate) + EVENT( EV_PlayIdle, hhGibbable::Event_PlayIdle) + EVENT( EV_Respawn, hhGibbable::Event_Respawn) +END_CLASS + +// Called during idEntity::Spawn +void hhGibbable::SetModel( const char *modelname ) { + // NLATODO - Is this called still? + hhAnimatedEntity::SetModel( modelname ); + + bool bAnimates = spawnArgs.FindKey("anim idle") != NULL; +} + +void hhGibbable::Spawn(void) { + + bVertexColorFade = spawnArgs.GetBool("materialFade"); + if (bVertexColorFade) { + SetDeformation(DEFORMTYPE_VERTEXCOLOR, 1.0f); + } + + //HUMANHEAD: aob - Flynn wanted some gibbables to be triggered only + fl.takedamage = !spawnArgs.GetBool("noDamage", "0"); + + // setup the clipModel +// GetPhysics()->SetContents( CONTENTS_SOLID ); + GetPhysics()->SetContents( CONTENTS_BODY | CONTENTS_RENDERMODEL ); + + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + + idleChannel = GetChannelForAnim( "idle" ); + painChannel = GetChannelForAnim( "pain" ); + + PostEventMS(&EV_PlayIdle, 1000); +} + +void hhGibbable::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( idleChannel ); + savefile->WriteInt( painChannel ); + savefile->WriteBool( bVertexColorFade ); +} + +void hhGibbable::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( idleChannel ); + savefile->ReadInt( painChannel ); + savefile->ReadBool( bVertexColorFade ); +} + +bool hhGibbable::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + // Adjust vertex color + if (bVertexColorFade) { + float fadeAlpha = idMath::ClampFloat(0.0f, 1.0f, ((float)health / spawnArgs.GetFloat("health"))); + SetDeformation(DEFORMTYPE_VERTEXCOLOR, fadeAlpha); + } + + if (painAnim) { + GetAnimator()->PlayAnim( painChannel, painAnim, gameLocal.time, 0); + } + + return( hhAnimatedEntity::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhGibbable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + Explode(attacker); +} + +int hhGibbable::DetermineThinnestAxis() { + int best = 0; + idBounds bounds = GetPhysics()->GetBounds(); + for( int i = 1; i < 3; ++i ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + + return best; +} + +void hhGibbable::Explode(idEntity *activator) { + hhFxInfo fxInfo; + + Hide(); + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + ActivateTargets( activator ); + SetSkinByName(NULL); + if ( spawnArgs.GetFloat( "respawn", "0" ) ) { + PostEventSec( &EV_Respawn, spawnArgs.GetFloat( "respawn", "0" ) ); + } else { + PostEventMS( &EV_Remove, 200 ); // Remove after a small delay to allow sound commands to execute + } + StartSound( "snd_gib", SND_CHANNEL_ANY ); + + // Find thinnest axis in the bounds and use for fx normal + idVec3 thinnest = vec3_origin; + int axisIndex = DetermineThinnestAxis(); + thinnest[axisIndex] = 1.0f; + thinnest *= GetAxis(); + + fxInfo.RemoveWhenDone( true ); + fxInfo.SetNormal(thinnest); + // Spawn FX system for gib + BroadcastFxInfo( spawnArgs.GetString("fx_gib"), GetOrigin(), GetAxis(), &fxInfo ); + + // Spawn gibs + if (spawnArgs.FindKey("def_debrisspawner")) { + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), this ); + } +} + +void hhGibbable::Event_Respawn() { + GetPhysics()->SetContents( CONTENTS_BODY | CONTENTS_RENDERMODEL ); + fl.takedamage = true; + Show(); +} + +void hhGibbable::Event_Activate( idEntity *activator ) { + Explode(activator); +} + +void hhGibbable::Event_PlayIdle( void ) { + if (idleAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->CycleAnim( idleChannel, idleAnim, gameLocal.time, 0); + } +} + diff --git a/neo/prey/Prey/game_gibbable.h b/neo/prey/Prey/game_gibbable.h new file mode 100644 index 00000000..6283a5a6 --- /dev/null +++ b/neo/prey/Prey/game_gibbable.h @@ -0,0 +1,31 @@ +#ifndef __PREY_GIBBABLE_H +#define __PREY_GIBBABLE_H + +class hhGibbable : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhGibbable ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Explode(idEntity *activator); + 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 ); + virtual void SetModel( const char *modelname ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_PlayIdle( void ); + void Event_Respawn( void ); + int DetermineThinnestAxis(); + +protected: + int idleAnim; + int painAnim; + + int idleChannel; + int painChannel; + bool bVertexColorFade; +}; + +#endif diff --git a/neo/prey/Prey/game_gravityswitch.cpp b/neo/prey/Prey/game_gravityswitch.cpp new file mode 100644 index 00000000..b05aa09b --- /dev/null +++ b/neo/prey/Prey/game_gravityswitch.cpp @@ -0,0 +1,204 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_CheckAgain( "", NULL ); + +CLASS_DECLARATION( hhDamageTrigger, hhGravitySwitch ) + EVENT( EV_PostSpawn, hhGravitySwitch::Event_PostSpawn ) + EVENT( EV_CheckAgain, hhGravitySwitch::Event_CheckAgain ) +END_CLASS + + +void hhGravitySwitch::Spawn( void ) { + fl.networkSync = true; + + fl.clientEvents = true; + + CancelEvents(&EV_PostSpawn); // Parent actually already posted one + PostEventMS(&EV_PostSpawn, 0); +} + +void hhGravitySwitch::Event_PostSpawn(void) { + idVec3 origin = GetOrigin() - GetAxis()[0]*10.0f; + + // Determine axis of emitter + // NOTE: axis of gravityswitch is reversed, axis of emitters is: identity==up + idVec3 up(0.0f, 0.0f, 1.0f); + idVec3 back(-1.0f, 0.0f, 0.0f); + idMat3 axis = up.ToMat3().Inverse()*back.ToMat3().Inverse()*GetAxis(); //a mess to * axis to get the emitter to point the same way as the model + + idDict args; + args.Clear(); + args.SetVector("origin", origin); + args.SetMatrix("rotation", axis); + + const char *effectName = spawnArgs.GetString("def_effect", NULL); + if (effectName && *effectName) { + effect = static_cast(gameLocal.SpawnClientObject(effectName, &args) ); + if (effect.IsValid()) { + effect->Hide(); + } + } +} + +hhGravitySwitch::~hhGravitySwitch() { + SAFE_REMOVE( effect ); +} + +void hhGravitySwitch::Save(idSaveGame *savefile) const { + effect.Save(savefile); +} + +void hhGravitySwitch::Restore( idRestoreGame *savefile ) { + effect.Restore(savefile); +} + +void hhGravitySwitch::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetBool("radius")) { + return; // Gravity switches are immune to all splash damage + } + hhDamageTrigger::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + +idVec3 hhGravitySwitch::GetGravityVector() { + idVec3 vector; + + if (!spawnArgs.GetVector("vector", NULL, vector)) { + float strength = spawnArgs.GetFloat("strength", "1"); + idVec3 direction = GetPhysics()->GetAxis()[0]; + vector = direction * strength * DEFAULT_GRAVITY; + } + + return vector; +} + +void hhGravitySwitch::SetGravityVector(idEntity *activator) { + idVec3 newGravity = GetGravityVector(); + bool bSwitchedGravity = false; + + int numTargets = targets.Num(); + for ( int ix = 0; ix < numTargets; ix++) { + idEntity *ent = targets[ix].GetEntity(); + if (ent && ent->IsType(hhGravityZone::Type)) { + hhGravityZone *zone = static_cast(ent); + + idVec3 zoneGravity = zone->GetDestinationGravity(); + if (!zoneGravity.Compare(newGravity, VECTOR_EPSILON)) { + zone->SetGravityOnZone( newGravity ); + bSwitchedGravity = true; + } + } + } + + if (bSwitchedGravity) { + if (gameLocal.isMultiplayer && !gameLocal.isClient) { //rww - play sound when shot in MP + StartSound("snd_gravity_mpshot", SND_CHANNEL_ANY, 0, true); + } + ActivateTargets(activator); + BecomeActive(TH_TICKER); + } +} + +void hhGravitySwitch::Ticker() { + // See if our gravity zone(s) are following our gravity + int numTargets = 1; // only need to check one, they are all assumed to be the same. If not, we have problems anyway. + for ( int ix = 0; ix < numTargets; ix++) { + idEntity *ent = targets[ix].GetEntity(); + if (ent && ent->IsType(hhGravityZone::Type)) { + hhGravityZone *zone = static_cast(ent); + if (zone->GetDestinationGravity().Compare( GetGravityVector(), VECTOR_EPSILON )) { + // Zone is still following my gravity, emit smoke + if (effect.IsValid() && effect->IsHidden()) { + effect->Show(); + } + return; + } + } + } + + // If not, stop thinking about it. + BecomeInactive(TH_TICKER); + if (effect.IsValid() && !effect->IsHidden()) { + effect->Hide(); + } + + // Check again in a while in case another switch turns the zone onto my gravity + CancelEvents(&EV_CheckAgain); + PostEventMS(&EV_CheckAgain, 1000); +} + +void hhGravitySwitch::Event_CheckAgain() { + BecomeActive(TH_TICKER); // Check again +} + +void hhGravitySwitch::TriggerAction(idEntity *activator) { + + SetGravityVector(activator); + + // Handle default trigger behavior + // Hack, make targetlist appear to be empty so ActivateTargets() does nothing, we handle this ourself + int num = targets.Num(); + targets.SetNum(0, false); + hhDamageTrigger::TriggerAction(activator); + targets.SetNum(num, false); +} + +void hhGravitySwitch::Event_Enable() { + if (!bEnabled) { + StartSound("snd_gravity_enable", SND_CHANNEL_ANY, 0, true); + } + SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); + SetShaderParm(SHADERPARM_MISC, 1); + bEnabled = true; + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_IKCLIP|CONTENTS_SHOOTABLEBYARROW ); + BecomeActive(TH_TICKER); +} + +void hhGravitySwitch::Event_Disable() { + if (bEnabled) { + StartSound("snd_gravity_disable", SND_CHANNEL_ANY, 0, true); + } + SetShaderParm(SHADERPARM_MISC, 0); + GetPhysics()->SetContents( CONTENTS_IKCLIP ); + fl.takedamage = false; + bEnabled = false; + BecomeInactive(TH_TICKER); + if (effect.IsValid()) { + effect->Hide(); + } +} + +void hhGravitySwitch::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(bEnabled, 1); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MISC]); + + if (effect.IsValid()) { + msg.WriteBits(effect->IsHidden(), 1); + } + else { + msg.WriteBits(0, 1); + } +} + +void hhGravitySwitch::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + bEnabled = !!msg.ReadBits(1); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_MISC] = msg.ReadFloat(); + + bool fxHidden = !!msg.ReadBits(1); + if (effect.IsValid() && fxHidden != effect->IsHidden()) { + if (fxHidden) { + effect->Hide(); + } + else { + effect->Show(); + } + } +} diff --git a/neo/prey/Prey/game_gravityswitch.h b/neo/prey/Prey/game_gravityswitch.h new file mode 100644 index 00000000..f744d6f8 --- /dev/null +++ b/neo/prey/Prey/game_gravityswitch.h @@ -0,0 +1,35 @@ +#ifndef __GAME_GRAVITYSWITCH_H__ +#define __GAME_GRAVITYSWITCH_H__ + +class hhGravitySwitch : public hhDamageTrigger { +public: + CLASS_PROTOTYPE( hhGravitySwitch ); + + virtual ~hhGravitySwitch(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // Overridden methods + virtual void Ticker(); + virtual void Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location); + virtual void TriggerAction(idEntity *activator); + virtual void Event_Enable(); + virtual void Event_Disable(); + virtual void Event_PostSpawn(); + void Event_CheckAgain(); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + void SetGravityVector(idEntity *activator); + idVec3 GetGravityVector(); + +private: + idEntityPtr effect; + +}; + +#endif // __GAME_GRAVITYSWITCH_H__ diff --git a/neo/prey/Prey/game_guihand.cpp b/neo/prey/Prey/game_guihand.cpp new file mode 100644 index 00000000..86178b31 --- /dev/null +++ b/neo/prey/Prey/game_guihand.cpp @@ -0,0 +1,52 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhHand, hhGuiHand ) +END_CLASS + + +void hhGuiHand::Spawn(void) { + actionAnimDoneTime = 0; + fl.networkSync = true; +} + +void hhGuiHand::Save(idSaveGame *savefile) const { + savefile->WriteInt( actionAnimDoneTime ); +} + +void hhGuiHand::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( actionAnimDoneTime ); +} + +void hhGuiHand::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + hhHand::WriteToSnapshot(msg); + + msg.WriteBits(actionAnimDoneTime, 32); +} + +void hhGuiHand::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhHand::ReadFromSnapshot(msg); + + actionAnimDoneTime = msg.ReadBits(32); +} + +void hhGuiHand::ClientPredictionThink( void ) { + hhHand::ClientPredictionThink(); +} + +void hhGuiHand::Action(void) { + PlayAnim( -1, action, &EV_Hand_Ready ); +} + +void hhGuiHand::SetAction(const char* str) { + action = str; +} + +bool hhGuiHand::IsValidFor( hhPlayer *who ) { + return( who->InGUIMode() ); +} diff --git a/neo/prey/Prey/game_guihand.h b/neo/prey/Prey/game_guihand.h new file mode 100644 index 00000000..2b089c5b --- /dev/null +++ b/neo/prey/Prey/game_guihand.h @@ -0,0 +1,33 @@ + +#ifndef __PREY_GAME_GUIHAND_H__ +#define __PREY_GAME_GUIHAND_H__ + +// Dumb forward decl +class hhPlayer; + +class hhGuiHand : public hhHand { + +public: + + CLASS_PROTOTYPE(hhGuiHand); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void Action(void); // Player clicked + virtual void SetAction(const char* str); //HUMANHEAD bjk + virtual bool IsValidFor( hhPlayer *who ); + +protected: + int actionAnimDoneTime; + const char* action; +}; + + +#endif /* __PREY_GAME_GUIHAND_H__ */ + diff --git a/neo/prey/Prey/game_gun.cpp b/neo/prey/Prey/game_gun.cpp new file mode 100644 index 00000000..71c8bcc7 --- /dev/null +++ b/neo/prey/Prey/game_gun.cpp @@ -0,0 +1,295 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhGun +// +//----------------------------------------------------------------------- +const idEventDef EV_SetEnemy("setenemy", "e"); +const idEventDef EV_FireGunAt("fireat", "e"); + +CLASS_DECLARATION(idEntity, hhGun) + EVENT( EV_Activate, hhGun::Event_Activate ) + EVENT( EV_SetEnemy, hhGun::Event_SetEnemy ) + EVENT( EV_FireGunAt, hhGun::Event_FireAt ) +END_CLASS + + +float AngleBetweenVectors(const idVec3 &v1, const idVec3 &v2) { + float dot = (v1 * v2) / ( v1.Length() * v2.Length() ); + return RAD2DEG(idMath::ACos(dot)); +} + +void hhGun::Spawn(void) { + coneAngle = spawnArgs.GetFloat("coneAngle"); + burstCount = spawnArgs.GetInt("bursts"); + burstRate = ( int )( spawnArgs.GetFloat( "burstRate" ) * 1000.0f ); + fireRate = ( int )( spawnArgs.GetFloat( "fireRate" ) * 1000.0f ); + fireDeviation = ( int )( spawnArgs.GetFloat( "fireDeviation" ) * 1000.0f ); + targetOffset = spawnArgs.GetVector("targetOffset"); + targetRadius = spawnArgs.GetFloat("targetRadius"); + nextFireTime = 0; + nextBurstTime = 0; + nextEnemyTime = 0; + firing = false; + enemyRate = 200; + + GetPhysics()->SetContents(CONTENTS_SOLID); + fl.takedamage = true; + + SetEnemy(NULL); + if (spawnArgs.GetBool("enabled")) { + BecomeActive(TH_THINK); + } +} + +void hhGun::Save(idSaveGame *savefile) const { + enemy.Save(savefile); + savefile->WriteInt( enemyRate ); + savefile->WriteInt( nextEnemyTime ); + savefile->WriteVec3( targetOffset ); + savefile->WriteFloat( targetRadius ); + savefile->WriteInt( fireRate ); + savefile->WriteInt( fireDeviation ); + savefile->WriteInt( nextFireTime ); + savefile->WriteInt( burstRate ); + savefile->WriteInt( burstCount ); + savefile->WriteInt( nextBurstTime ); + savefile->WriteInt( curBurst ); + savefile->WriteBool( firing ); + savefile->WriteFloat( coneAngle ); +} + +void hhGun::Restore( idRestoreGame *savefile ) { + enemy.Restore(savefile); + savefile->ReadInt( enemyRate ); + savefile->ReadInt( nextEnemyTime ); + savefile->ReadVec3( targetOffset ); + savefile->ReadFloat( targetRadius ); + savefile->ReadInt( fireRate ); + savefile->ReadInt( fireDeviation ); + savefile->ReadInt( nextFireTime ); + savefile->ReadInt( burstRate ); + savefile->ReadInt( burstCount ); + savefile->ReadInt( nextBurstTime ); + savefile->ReadInt( curBurst ); + savefile->ReadBool( firing ); + savefile->ReadFloat( coneAngle ); +} + +void hhGun::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + SetEnemy(NULL); + fl.takedamage = false; + BecomeInactive(TH_THINK); + + const char *killedModel = spawnArgs.GetString("model_killed", NULL); + if (killedModel) { + SetModel(killedModel); + UpdateVisuals(); + } + else { + GetPhysics()->SetContents(0); + } + + // Spawn gibs + if (spawnArgs.FindKey("def_debrisspawner")) { + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), this ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + ActivateTargets( attacker ); +} + +void hhGun::SetEnemy(idEntity *ent) { + if (ent && ent->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(ent); + enemy = player->InVehicle() ? player->GetVehicleInterface()->GetVehicle() : ent; + } + else { + enemy = ent; + } +} + +void hhGun::FindEnemy() { + if (enemy.IsValid() && enemy->GetHealth() > 0) { + return; + } + if ( !gameLocal.InPlayerPVS( this ) ) { + return; + } + + idEntity *bestEnt = NULL; + float bestDistSqr = idMath::INFINITY; + float radiusSqr = targetRadius*targetRadius; + idVec3 origin = GetPhysics()->GetOrigin(); + idMat3 axis = GetPhysics()->GetAxis(); + for ( int i = 0; i < MAX_CLIENTS ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + + if (ent) { + idVec3 toEnt = ent->GetPhysics()->GetOrigin() + targetOffset - origin; + float distSqr = toEnt.LengthSqr(); + if (distSqr < bestDistSqr && distSqr < radiusSqr && + AngleBetweenVectors(toEnt, axis[0]) < coneAngle) { + bestDistSqr = distSqr; + bestEnt = ent; + } + } + } + + SetEnemy(bestEnt); +} + +idMat3 hhGun::GetAimAxis() { +#if 0 + // Fast approximation + const idDict *projectileDef = declManager->FindEntityDef( spawnArgs.GetString("def_projectile") ); + float projSpeed = idProjectile::GetVelocity( projectileDef ).Length(); + idVec3 firePos = GetPhysics()->GetOrigin(); + idVec3 enemyPos = enemy->GetPhysics()->GetOrigin() + targetOffset; + idVec3 enemyVel = enemy->GetPhysics()->GetLinearVelocity(); + idVec3 toEnemy = enemyPos - firePos; + float enemyDist = toEnemy.Length(); + float projTime = enemyDist / projSpeed; + idVec3 predictedEnemyPos = enemyPos + enemyVel * projTime; + idVec3 aim = predictedEnemyPos - firePos; + aim.Normalize(); + return aim.hhToMat3(); +#else +/* Projectile prediction: + +Let: + Pe(t) = position of enemy at time t Ve = velocity of enemy (known) + Pm(t) = position of missile at time t Vm = velocity of missile (magnitude known) + Pf = position firing from + +(1) Pe(t) = Pe(0) + Ve * t Enemy position function + +(2) Aim(t) = Pe(t) - Pf Aim function is vector from firing point to Pe(t) + + |Aim(t)| distance / distance/sec -> sec +(3) -------- = t + |Vm| + +(4) d = Pe(0) - Pf delta vector from firing point to initial enemy position + +(5) s = |Vm| + + Substituting and simplifying yields a quadratic function in terms of t, Ve, d, & s: + At² + Bt + C = 0 +*/ + const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_projectile") ); + if ( !projectileDef ) { + gameLocal.Error( "Unknown def_projectile: %s\n", spawnArgs.GetString("def_projectile") ); + } + float projSpeed = idProjectile::GetVelocity( projectileDef ).Length(); + idVec3 firePos = GetPhysics()->GetOrigin(); + idVec3 enemyPos = enemy->GetOrigin() + idVec3(0,0,32); + idVec3 enemyVel = enemy->GetPhysics()->GetLinearVelocity(); + idVec3 d = enemyPos - firePos; + idVec3 v = enemyVel; + float s = projSpeed; + + float a = v.x*v.x + v.y*v.y + v.z*v.x - s*s; // t2 term + float b = 2 * (d.x*v.x + d.y*v.y + d.z*v.z); // t term + float c = d.x*d.x + d.y*d.y + d.z*d.z; + + // Use quadratic formula to solve for t + float t1 = (-b + sqrt(b*b - 4*a*c) ) / (2*a); + float t2 = (-b - sqrt(b*b - 4*a*c) ) / (2*a); + float projTime = t1 > 0 ? t1 : t2; + + idVec3 predictedEnemyPos = enemyPos + enemyVel * projTime; + idVec3 aim = predictedEnemyPos - firePos; + aim.Normalize(); + return aim.hhToMat3(); +#endif +} + +bool hhGun::ValidEnemy() { + return (enemy.IsValid()) ? enemy->GetHealth() > 0 : false; +} + +void hhGun::Fire(idMat3 &axis) { + if (health > 0) { + hhUtils::LaunchProjectile(this, spawnArgs.GetString("def_projectile"), axis, GetOrigin()); + StartSound( "snd_fire", SND_CHANNEL_ANY ); + } +} + +void hhGun::Think(void) { + if (thinkFlags & TH_THINK) { + + // Temp: for placement + if (spawnArgs.GetBool("showCone")) { + float radius = targetRadius * tan(DEG2RAD(coneAngle)); + gameRenderWorld->DebugCone(colorGreen, + GetPhysics()->GetOrigin(), + GetPhysics()->GetAxis()[0] * targetRadius, + 0, radius); + } + + if (!ValidEnemy() && gameLocal.time >= nextEnemyTime ) { + FindEnemy(); + nextEnemyTime = gameLocal.time + enemyRate; + } + + if (ValidEnemy()) { + if (!firing && gameLocal.time >= nextFireTime ) { + firing = true; + curBurst = burstCount; + nextFireTime = gameLocal.time + fireRate + gameLocal.random.CRandomFloat()*fireDeviation; + nextBurstTime = gameLocal.time; + } + + if (firing && gameLocal.time >= nextBurstTime ) { + // Aim + idMat3 aimAxis = GetAimAxis(); + + // Burst if enemy is in cone + if ( AngleBetweenVectors(aimAxis[0], GetPhysics()->GetAxis()[0]) < coneAngle ) { + Fire(aimAxis); + if (--curBurst <= 0) { + firing = false; + } + } + else { + firing = false; + SetEnemy(NULL); + } + + nextBurstTime = gameLocal.time + burstRate; + } + } + } + idEntity::Think(); +} + +void hhGun::Event_Activate(idEntity *activator) { + if (targets.Num() && targets[0].IsValid()) { + Event_FireAt(targets[0].GetEntity()); + return; + } + + if (thinkFlags & TH_THINK) { + BecomeInactive(TH_THINK); + } + else { + BecomeActive(TH_THINK); + } +} + +void hhGun::Event_SetEnemy(idEntity *newEnemy) { + SetEnemy(newEnemy); +} + +void hhGun::Event_FireAt(idEntity *victim) { + idVec3 dir = victim->GetOrigin() - GetOrigin(); + dir.Normalize(); + Fire(dir.ToMat3()); +} diff --git a/neo/prey/Prey/game_gun.h b/neo/prey/Prey/game_gun.h new file mode 100644 index 00000000..0e2c300a --- /dev/null +++ b/neo/prey/Prey/game_gun.h @@ -0,0 +1,46 @@ + +#ifndef __GAME_GUN_H__ +#define __GAME_GUN_H__ + +class hhGun : public idEntity { +public: + CLASS_PROTOTYPE( hhGun ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location); + void SetEnemy(idEntity *ent); + void FindEnemy(); + idMat3 GetAimAxis(); + bool ValidEnemy(); + +protected: + void Fire(idMat3 &axis); + + void Event_Activate(idEntity *activator); + void Event_SetEnemy(idEntity *newEnemy); + void Event_FireAt(idEntity *victim); + +protected: + idEntityPtr enemy; + int enemyRate; + int nextEnemyTime; + idVec3 targetOffset; + float targetRadius; + + int fireRate; + int fireDeviation; + int nextFireTime; + + int burstRate; + int burstCount; + int nextBurstTime; + int curBurst; + bool firing; + float coneAngle; +}; + +#endif // __GAME_GUN_H__ diff --git a/neo/prey/Prey/game_hand.cpp b/neo/prey/Prey/game_hand.cpp new file mode 100644 index 00000000..beea9f27 --- /dev/null +++ b/neo/prey/Prey/game_hand.cpp @@ -0,0 +1,1155 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +const idEventDef EV_Hand_DoRemove( "DoHandRemove", NULL ); +const idEventDef EV_Hand_DoAttach( "DoHandAttach", "e" ); +const idEventDef EV_Hand_Remove( "RemoveHand", NULL ); + +const idEventDef EV_Hand_Ready( "" ); +const idEventDef EV_Hand_Lowered( "" ); +const idEventDef EV_Hand_Raise( "" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhHand ) + EVENT ( EV_Hand_DoRemove, hhHand::Event_DoHandRemove ) + EVENT ( EV_Hand_DoAttach, hhHand::Event_DoHandAttach ) + EVENT ( EV_Hand_Remove, hhHand::Event_RemoveHand ) + EVENT ( EV_Hand_Ready, hhHand::Event_Ready ) + EVENT ( EV_Hand_Lowered, hhHand::Event_Lowered ) + EVENT ( EV_Hand_Raise, hhHand::Event_Raise ) +END_CLASS + +/* +============ +hhHand::~hhHand +============ +*/ +hhHand::~hhHand() { + bool shouldWarn = (gameLocal.GameState() != GAMESTATE_SHUTDOWN && !gameLocal.isClient); + //rww - don't warn about this on client, since snapshot entities are free to be removed + //at any time. + + // Check if the hand deleted is still in the hands list + if ( gameLocal.hands.Find( this ) ) { + if ( shouldWarn ) { + gameLocal.Warning( "The hand (%p/%s) was deleted before being removed", + this, spawnArgs.GetString( "classname" ) ); + } + gameLocal.hands.Remove( this ); + } + + if ( owner != NULL ) { + if ( shouldWarn ) { + gameLocal.Warning( "A hand (%p) was deleted while having an owner", this ); + } + } + + StopSound( SND_CHANNEL_ANY ); +} + + +/* +============ +hhHand::Spawn +============ +*/ +void hhHand::Spawn( void ) { + gameLocal.hands.Append( idEntityPtr (this) ); + + owner = NULL; + previousHand = NULL; + + renderEntity.weaponDepthHack = true; + + animEvent = NULL; + + priority = spawnArgs.GetInt("priority"); + + status = HS_UNKNOWN; + idealState = HS_READY; + + animBlendFrames = 4; // needs blending + animDoneTime = 0; + + attached = false; + attachTime = -1; + + spawnArgs.GetBool( "replace_previous", "0", replacePrevious ); + + spawnArgs.GetBool( "lower_weapon", "1", lowerWeapon ); + spawnArgs.GetBool( "aside_weapon", "1", asideWeapon ); + + handedness = spawnArgs.GetInt( "handedness", "2" ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( NULL, 1.0f ); + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + SetPhysics( &physicsObj ); + + // Lower overrides aside + if ( lowerWeapon && asideWeapon ) { + asideWeapon = false; + } + + Hide(); +} + +void hhHand::Save(idSaveGame *savefile) const { + //savefile->WriteObject( owner ); + owner.Save(savefile); + savefile->WriteInt( priority ); + savefile->WriteObject( previousHand ); + savefile->WriteBool( lowerWeapon ); + savefile->WriteBool( asideWeapon ); + savefile->WriteBool( attached ); + savefile->WriteBool( replacePrevious ); + savefile->WriteInt( attachTime ); + savefile->WriteInt( handedness ); + savefile->WriteInt( animDoneTime ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( status ); + savefile->WriteInt( idealState ); + savefile->WriteEventDef(animEvent); + savefile->WriteStaticObject( physicsObj ); +} + +void hhHand::Restore( idRestoreGame *savefile ) { + //savefile->ReadObject( reinterpret_cast(owner) ); + owner.Restore(savefile); + savefile->ReadInt( priority ); + savefile->ReadObject( reinterpret_cast(previousHand) ); + savefile->ReadBool( lowerWeapon ); + savefile->ReadBool( asideWeapon ); + savefile->ReadBool( attached ); + savefile->ReadBool( replacePrevious ); + savefile->ReadInt( attachTime ); + savefile->ReadInt( handedness ); + savefile->ReadInt( animDoneTime ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( status ); + savefile->ReadInt( idealState ); + savefile->ReadEventDef(animEvent); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +//rww - network code +void hhHand::WriteToSnapshot( idBitMsgDelta &msg ) const { + //physicsObj.WriteToSnapshot(msg); + //rww - trying to keep this all local + + WriteBindToSnapshot(msg); + + msg.WriteBits(owner.GetSpawnId(), 32); + + if (previousHand) { + msg.WriteBits(gameLocal.GetSpawnId(previousHand), 32); + } + else { + msg.WriteBits(0, 32); + } + + msg.WriteBits(attached, 1); + msg.WriteBits(replacePrevious, 1); + //msg.WriteBits(attachTime, 32); + //msg.WriteBits(animDoneTime, 32); + //msg.WriteBits(animBlendFrames, 32); + + assert(idealState < (1<<4)); + msg.WriteBits(idealState, 4); + + assert(status < (1<<4)); + msg.WriteBits(status, 4); + + msg.WriteBits(IsHidden(), 1); + + assert(gameLocal.hands.Num() < (1<<4)); + msg.WriteBits(gameLocal.hands.Num(), 4); + int i = 0; + while (i < gameLocal.hands.Num()) { + hhHand *hand = gameLocal.hands[i].GetEntity(); + + if (hand) { + msg.WriteBits(gameLocal.GetSpawnId(hand), 32); + } + else { + msg.WriteBits(0, 32); + } + + i++; + } + + msg.WriteBits(renderEntity.allowSurfaceInViewID, GENTITYNUM_BITS); + + idAnimatedEntity::WriteToSnapshot(msg); +} + +void hhHand::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //physicsObj.ReadFromSnapshot(msg); + //rww - trying to keep this all local + + ReadBindFromSnapshot(msg); + + if (owner.SetSpawnId(msg.ReadBits(32))) { + physicsObj.SetSelfOwner(owner.GetEntity()); + if (owner.IsValid() && owner.GetEntity()) { + DoHandAttach(owner.GetEntity()); + } else { + owner = NULL; + } + } + + //Show(); + + idEntityPtr newPrevHand; + int prevHandSpawnId = msg.ReadBits(32); + if (newPrevHand.SetSpawnId(prevHandSpawnId)) { + previousHand = newPrevHand.GetEntity(); + } + else { + previousHand = NULL; + } + + attached = !!msg.ReadBits(1); + replacePrevious = !!msg.ReadBits(1); + //attachTime = msg.ReadBits(32); + //animDoneTime = msg.ReadBits(32); + //animBlendFrames = msg.ReadBits(32); + + idealState = msg.ReadBits(4); + + int nextStatus = msg.ReadBits(4); + if (status != nextStatus) { + if (nextStatus == HS_LOWERING || nextStatus == HS_LOWERED) { + PutAway(); + } + else if (nextStatus == HS_RAISING) { + Raise(); + } + else if (nextStatus == HS_READY) { + //PlayAnim( -1, "idle" ); + Ready(); + } + status = nextStatus; + } + + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } else { + Show(); + } + } + + int numHands = msg.ReadBits(4); + gameLocal.hands.SetNum(numHands); + int i = 0; + while (i < numHands) { + int handSpawnId = msg.ReadBits(32); + gameLocal.hands[i].SetSpawnId( handSpawnId ); + + i++; + } + + renderEntity.allowSurfaceInViewID = msg.ReadBits(GENTITYNUM_BITS); + + idAnimatedEntity::ReadFromSnapshot(msg); + + /* + if (msg.HasChanged()) { + Present(); + } + */ +} + +void hhHand::ClientPredictionThink( void ) { + BecomeActive(TH_THINK|TH_ANIMATE); + UpdateVisuals(); + idAnimatedEntity::ClientPredictionThink(); +} + +bool hhHand::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch (event) { + case EVENT_REMOVEHAND: { + RemoveHand(); + return true; + } + default: { + return hhAnimatedEntity::ClientReceiveEvent(event, time, msg); + } + } +} + +/* +============ +hhHand::Event_Ready +============ +*/ +void hhHand::Event_Ready() { + Show(); + Ready(); +} + + +/* +=========== +hhHand::Event_Lowered +=========== +*/ +void hhHand::Event_Lowered() { + status = HS_LOWERED; + Hide(); +} + + +/* +============ +hhHand::Event_Raise +============ +*/ +void hhHand::Event_Raise() { + Raise(); +} + + +/* +============ +hhHand::Ready +============ +*/ +void hhHand::Ready() { + + switch ( idealState ) { + + case HS_READY: + CycleAnim(-1, "idle", 500); + status = HS_READY; + break; + + case HS_LOWERED: + PutAway(); + break; + + default: + gameLocal.Warning( "hhHand::Ready: Unsupported ideal state: %d", idealState ); + break; + + } +} + + +/* +============ +hhHand::Raise +============ +*/ +void hhHand::Raise() { + + Show(); + + if ( IsRaising() ) { // If already raising, return + return; + } + + status = HS_RAISING; + PlayAnim( -1, "raise", &EV_Hand_Ready ); +} + + +/* +============ +hhHand::PutAway +============ +*/ +void hhHand::PutAway() { + + if ( IsLowering() ) { + // already being put away, so don't play more than one put away animations + return; + } + + status = HS_LOWERING; + PlayAnim( -1, "putaway", &EV_Hand_Lowered ); +} + + +/* +=============== +hhHand::CycleAnim +=============== +*/ +void hhHand::CycleAnim( int channel, const char *animname, int blendTime ) { + if ( channel < 0 ) { + channel = GetChannelForAnim( animname ); + } + + int anim = GetAnimator()->GetAnim( animname ); + if ( anim ) { + GetAnimator()->CycleAnim( channel, anim, gameLocal.time, blendTime ); + } +} + +/* +=============== +hhHand::PlayAnim +=============== +*/ +void hhHand::PlayAnim( int channel, const char *animname, const idEventDef *event ) { + int anim; + + // HUMANHEAD nla + if ( animEvent ) { + CancelEvents( animEvent ); + } + animEvent = event; + + if ( channel < 0 ) { + channel = GetChannelForAnim( animname ); + } + // HUMANHEAD END + + anim = GetAnimator()->GetAnim( animname ); + if ( anim ) { + GetAnimator()->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = GetAnimator()->CurrentAnim( channel )->GetEndTime(); + } else { + // This is a valid case, some hands (mounted gun) don't animate + GetAnimator()->Clear( channel, gameLocal.time, FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } + animBlendFrames = 4; + + // HUMANHEAD nla + if ( animEvent ) { + PostEventMS( animEvent, animDoneTime - gameLocal.time ); + } + // HUMANHEAD END +} + + +/* +============ +hhHand::AttachHand +Returns false if the hand can not be attached +============ +*/ +bool hhHand::AttachHand( hhPlayer *player, bool attachNow ) { + assert(player); + + if ( !CheckHandAttach( player ) ) { + return( false ); + } + + int loweringFinished = LowerHandOrWeapon( player ); + SetOwner( player ); + Bind( player, true ); + + if ( loweringFinished >= 0 ) { + player->handNext = this; + // If the attachment should wait, and we don't want to force it to happen now + if ( loweringFinished - gameLocal.time > 0 && !attachNow ) { + PostEventMS( &EV_Hand_DoAttach, loweringFinished - gameLocal.time, player ); + } + else { + Event_DoHandAttach( player ); + } + attachTime = loweringFinished; + } + + return( true ); +} + +/* +============ +hhHand::LowerHandOrWeapon + Used when the hand adds itself. + returns the time when the lowering will be done + will be -1 if the hand has already been attached. +============ +*/ +int hhHand::LowerHandOrWeapon( hhPlayer *player ) { + int animDone = gameLocal.time; + int animDone2; + + // If there is a 'next' hand, we can just replace it, as it is already playing the down anim for the hand + if ( player->handNext.IsValid() ) { + animDone = player->handNext->GetAttachTime(); + + gameLocal.hands.Remove( player->handNext.GetEntity() ); + + // Clear the old hand of any pending events, and then remove it + player->handNext->SetOwner( NULL ); + player->handNext->CancelEvents( NULL ); + player->handNext->ProcessEvent( &EV_Remove ); + + player->handNext = NULL; + } + // Else, If there is just a hand, play the down anim on it or replace it + else if ( player->hand.IsValid() ) { + //! Work out this logic more fully + if ( replacePrevious ) { + ReplaceHand( player ); + animDone = -1; + } + // If it isn't on it's way down, move it down + if ( !player->hand->IsLowered() ) { + player->hand->PutAway(); + animDone = player->hand->GetAnimDoneTime(); + } + } + + animDone2 = HandleWeapon( player, this, animDone - gameLocal.time ); + if ( ( animDone >= 0 ) && ( animDone2 > animDone ) ) { + animDone = animDone2; + } + // If there is a weapon play the down anim on it + /* + if ( player->hhweapon ) { + if ( lowerWeapon && !player->hhweapon->IsLowered() ) { + player->hhweapon->PutAway(); + animDone2 = player->hhweapon->GetAnimDoneTime(); + if ( animDone2 > animDone ) { + animDone = animDone2; + } + } + else if ( asideWeapon && !player->hhweapon->IsAside() ) { + player->hhweapon->PutAside(); + // When aside, we want to have it play right away, so dont' change animDone + } + // gameLocal.Printf("Scheduled down for %.2f\n", animDone); + } + */ + + return( animDone ); +} + +/* +============== +RaiseHandOrWeapon + + Assumes the hand is all the way down +============== +*/ +void hhHand::RaiseHandOrWeapon( hhPlayer *player ) { + hhHand *theHand = NULL; + int raiseDelay = 0; + + // Get the next hand + theHand = this->GetPreviousHand( player ); + + raiseDelay = HandleWeapon( player, theHand ); + + if ( theHand && ( !theHand->IsRaising() || !theHand->IsReady() ) ) { + theHand->PostEventMS( &EV_Hand_Raise, raiseDelay - gameLocal.time ); + } +} + + +/* +================ +hhHand::Reraise + Raises the hand again. +================ +*/ +void hhHand::Reraise( ) { + hhPlayer *thePlayer = NULL; + int raiseDelay = 0; + + CancelEvents( &EV_Remove ); + CancelEvents( &EV_Hand_Raise ); + + if ( owner.IsValid() && owner.GetEntity() && owner->IsType( hhPlayer::Type ) ) { + thePlayer = ( hhPlayer * ) owner.GetEntity(); + } + else { + gameLocal.Printf( "hhHand::Reraise: Warning tried to reraise when not on an hhPlayer (%p)\n", owner ); + return; + } + + raiseDelay = HandleWeapon( thePlayer, this ); + + if ( !IsRaising() || !IsReady() ) { + PostEventMS( &EV_Hand_Raise, raiseDelay - gameLocal.time ); + } +} + + +/* +=========== +hhHand::GetPreviousHand +=========== +*/ +hhHand *hhHand::GetPreviousHand( hhPlayer *player ) { + hhHand *prevHand = NULL; + + // If there is no player, just give them our previous hand + if ( player == NULL ) { + return( previousHand ); + } + + // If we are the next hand, up the current hand + if ( player->handNext == this ) { + prevHand = player->hand.GetEntity(); + } + // Else If we are the current hand + else if ( player->hand == this ) { + // If we have a previous hand, raise it if it isn't already up or being raised + if ( previousHand ) { + prevHand = previousHand; + } + } + // Else we are neither, throw a warning. How are we here? + else { + gameLocal.Warning( "ERROR GetPreviousHand: ERROR: Tried to get a previous hand when we (%p) are not on the player (%p/%p)", + this, player->handNext, player->hand ); + } + + return( prevHand ); +} + + +/* +============ +hhHand::HandleWeapon + Set the weapon in the proper place for the hand + Assumes the hand in question will be raised. + Returns time (MS) to wait until the hand can be raised. Only in the + case of the weapon being lowered does it really matter/ > 0 +============ +*/ +int hhHand::HandleWeapon( hhPlayer *player, hhHand *hand, int weaponDelay, bool doNow ) { + int raiseHandDelay = gameLocal.time; + bool weaponReady = ( hand == NULL ) && !player->ChangingWeapons(); + + // If we don't have a weapon, then just return + if ( !player->weapon.IsValid() || player->GetIdealWeapon() == 0 ) { + return( 0 ); + } + + // If we have a hand to compare to + if ( hand ) { + // If the hand wants the weapon lowered + if ( hand->lowerWeapon || ( hand->handedness & player->weapon->GetHandedness() ) ) { + player->weapon->PutAway(); + if ( doNow ) { + player->weapon->SetState( "Down", 0 ); + } + else { + // Foce the gun to go, so we can know when it'll be done playing the anim. + player->weapon->UpdateScript(); + raiseHandDelay = player->weapon->GetAnimDoneTime(); + } + /* + // If it isn't lowered, lower it + if ( !player->weapon->IsLowered() ) { + player->weapon->PutAway(); + raiseHandDelay = player->weapon->GetAnimDoneTime(); + } + */ + } //. lower the weapon + // If the hand wants the weapon to the side + else if ( hand->asideWeapon ) { + player->weapon->PutAside(); + if ( doNow ) { + player->weapon->SetState( "Aside", 0 ); + } + // We only want a delay if the weapon should pop up? + else if ( player->weapon->IsLowered() ) { + // Foce the gun to go, so we can know when it'll be done playing the anim. + player->weapon->UpdateScript(); + raiseHandDelay = player->weapon->GetAnimDoneTime(); + player->weapon->SetState( "PutAside", 4 ); + } + /* + // If it is lowered, + if ( player->weapon->IsLowered() ) { + // First raise it, then set it aside + int done = weaponDelay; + idAnim *anim = player->weapon->GetAnimator()->GetAnim( "raise" ); // UGLY Hack, as hardcoded to the name + + player->weapon->PostEventMS( &EV_Weapon_WeaponRising, weaponDelay ); + if ( anim ) { done = anim->Length(); } + player->weapon->PostEventMS( &EV_Weapon_Aside, done ); + } + // Else if it is ready/upright and not aside + else if ( !player->weapon->IsAside() ) { + // Just set it aside + player->weapon->PutAside(); + } + */ + } + // Weapon should be in ready mode + else { + weaponReady = true; + } + } // Valid hand + + // If we don't have a hand, so just make sure the weapon is raised + if ( weaponReady ) { + player->weapon->Raise(); + if ( doNow ) { + player->weapon->SetState( "Idle", 0 ); + } + + /* + + // Clear any Aside events that may be posted. Happens when it was lowered, and the previous hand wanted aside + player->weapon->CancelEvents( &EV_Weapon_Aside ); + + // If the weapon is aside, put it upright + if ( player->weapon->IsAside() ) { + player->weapon->PutUpright(); + } + // Else if it is lowering, raise it + else if ( !player->weapon->IsRising() && player->weapon->IsLowered() ) { + player->weapon->Raise(); + } + // Else if we aren't ready, how can we not be??!? + else if ( player->weapon->IsLowered() || player->weapon->IsLowered() ) { + gameLocal.Warning( "hhHand::HandleWeapon ERROR: Have a weapon that is lowered/lowering when shouldn't be!" ); + } + */ + } + + return( raiseHandDelay ); +} + + +/* +============ +hhHand::RemoveHand + Plays the animations and schedules pointer changes +============ +*/ +bool hhHand::RemoveHand( void ) { + hhPlayer *player; + int animDone = gameLocal.time; + + if ( owner.IsValid() && owner.GetEntity() ) { + if ( owner->IsType( hhPlayer::Type ) ) { + player = static_cast( owner.GetEntity() ); + } + else { + gameLocal.Warning( "ERROR: RemoveHand: Tried to remove from a non player" ); + return( false ); + } + } + else { + gameLocal.Warning( "ERROR: RemoveHand: Tried to remove with no owner!!" ); + return( false ); + } + + if ( !CheckHandRemove( player ) ) { + return( false ); + } + + // Unaside is asap if we asided it, and the prev hand isn't gonna want it asided + // NLA - If we want to upright the GUI + hhHand *prevHand = GetPreviousHand( player ); + if ( ( !prevHand || !prevHand->asideWeapon ) && + ( asideWeapon && player->weapon.IsValid() && player->weapon->IsAside() ) ){ + player->weapon->PutUpright(); + } + + CancelEvents( &EV_Hand_DoRemove ); + + // Play down anim + if( !IsLowering() ) { + PutAway(); + animDone = GetAnimDoneTime(); + PostEventMS( &EV_Hand_DoRemove, animDone - gameLocal.time ); + } + else if ( IsLowered() ) { // If already down, just remove it now + Event_DoHandRemove(); + } + + return( true ); +} + + +/* +============ +hhHand::SetOwner +============ +*/ +void hhHand::SetOwner( idActor *owner ) { + + this->owner = owner; + + if (owner) { + if( GetPhysics() && GetPhysics()->IsType(hhPhysics_StaticWeapon::Type) ) { + static_cast(GetPhysics())->SetSelfOwner( owner ); + } + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = owner->entityNumber+1; + } +} + + +/* +============ +hhHand::ReplaceHand +============ +*/ +void hhHand::ReplaceHand( hhPlayer *player) { + //int animDone; + hhHand *hand = NULL; + + //! What to do if hand is null? + if( !player || !player->hand.IsValid() ) { + return; + } + hand = player->hand.GetEntity(); + + // Copy over any key info needed + this->previousHand = hand->previousHand; + + HandleWeapon( player, this ); + + player->hand = this; + hand->attached = false; + hand->owner = NULL; + hand->Hide(); + hand->PostEventMS( &EV_Remove, 0 ); + + gameLocal.hands.Remove( hand ); + + attached = true; + Show(); + Raise(); +} + + +/* +============ +hhHand::CheckHandAttach +============ +*/ +bool hhHand::CheckHandAttach( hhPlayer *owner ) { + bool debug = 0; + + if ( attached ) { + gameLocal.Warning( "Tried to attach an already attached hand." ); + return( false ); + } + + if( !owner ) { + if ( debug ) { gameLocal.Printf( "Out because of owner\n" ); } + return( false ); + } + + // More important hand there. Abort + if ( owner->hand.IsValid() && ( GetPriority() < owner->hand->GetPriority() ) ) { + if ( debug ) { gameLocal.Printf( "Out because of hand priority\n" ); } + return( false ); + } + + // More important hand about to be there. Abort + if ( owner->handNext.IsValid() && ( GetPriority() < owner->handNext->GetPriority() ) ) { + if ( debug ) { gameLocal.Printf( "Out because of next hand priority\n" ); } + return( false ); + } + + //? What do to if both equal priority? + if ( owner->hand.IsValid() && ( GetPriority() == owner->hand->GetPriority() ) ) { + // Same hand, abort! + if ( ( owner->hand->spawnArgs.GetString( "classname" ) == spawnArgs.GetString( "classname" ) ) && ( owner->hand != this ) ) { + if ( debug ) { gameLocal.Printf( "Out because of same hand\n" ); } + return( false ); + } + } + + //? What do to if both equal priority? + if ( owner->handNext.IsValid() && ( GetPriority() == owner->handNext->GetPriority() ) ) { + // Same hand, abort! + hhHand* currentHand = owner->handNext.GetEntity(); + if ( ( owner->handNext->spawnArgs.GetString( "classname" ) == spawnArgs.GetString( "classname" ) ) && ( owner->handNext != this ) ) { + if ( debug ) { gameLocal.Printf( "Out because of same next hand\n" ); } + return( false ); + } + } + + return( true ); +} + + +/* +============ +hhHand::CheckHandRemove +============ +*/ +bool hhHand::CheckHandRemove( hhPlayer *player ) { + + // Check have a valid player + if ( player == NULL ) { + gameLocal.Warning( "ERROR: DoHandRemove: We are not attached to a player!"); + return( false ); + } + + // We aren't here! + if ( IsAttached() && ( player->hand != this ) ) { + gameLocal.Warning( "ERROR: DoHandRemove: Tried to detach from an player we are not assigned to %d %p %p", + (int) IsAttached(), player->hand, this ); + return( false ); + } + + return( true ); +} + +/* +============ +hhHand::SetModel +============ +*/ +void hhHand::SetModel( const char *modelname ) { + hhAnimatedEntity::SetModel( modelname ); +} + +/* +============ +hhHand::Present +============ +*/ +void hhHand::Present() { + hhAnimatedEntity::Present(); +} + +/* +============ +hhHand::Event_DoHandAttach +============ +*/ +void hhHand::Event_DoHandAttach( idEntity *owner ) { + DoHandAttach( owner ); +} + +/* +============ +hhHand::DoHandAttach +============ +*/ +bool hhHand::DoHandAttach( idEntity *owner ) { + hhPlayer *player; + + if ( owner && owner->IsType( hhPlayer::Type ) ) { + player = static_cast( owner ); + } + else { + gameLocal.Warning("ERROR: Tried to attach to a non-player"); + return( false ); + } + + if ( !CheckHandAttach( player ) ) { + PostEventMS( &EV_Remove, 0 ); + return( false ); + } + + // Sanity check, we should be the top dogh + if ( player->handNext != this && !gameLocal.isClient ) { //rww - don't care on client, because we cannot rely on handNext + gameLocal.Warning( "ERROR: We (%p) should be the next hand (%p) but aren't", + this, player->handNext ); + } + + // Make sure nothing has changed + HandleWeapon( player, this ); + + // We are top dog, replace! :) + if ( !replacePrevious ) { + previousHand = player->hand.GetEntity(); + } + else { + previousHand = NULL; + } + player->hand = this; + player->handNext = NULL; + + attached = true; + attachTime = -1; + + Raise(); + Show(); + + return( true ); +} + +/* +============ +hhHand::Event_DoHandRemove +============ +*/ +void hhHand::Event_DoHandRemove( void ) { + DoHandRemove( ); +} + + +/* +============ +hhHand::ForceRemove + - Should only be called when you don't need other hands/weaspons adjusted +============ +*/ +void hhHand::ForceRemove( void ) { + + gameLocal.hands.Remove( this ); + + owner = NULL; + + Hide(); +} + +/* +============ +hhHand::DoHandRemove +============ +*/ +bool hhHand::DoHandRemove( void ) { + hhPlayer *player; + + gameLocal.hands.Remove( this ); + + if ( owner.IsValid() && owner.GetEntity() && owner->IsType( hhPlayer::Type ) ) { + player = static_cast( owner.GetEntity() ); + } + else { + gameLocal.Warning( "ERROR: DoHandRemove: Tried to remove from a non player" ); + return( false ); + } + + // Play up anim + RaiseHandOrWeapon( player ); + + if ( !CheckHandRemove( player ) ) { + return( false ); + } + + // Do the actual removal logic + Hide(); + PostEventMS( &EV_Remove, 0 ); + + if ( IsAttached() ) { + player->hand = previousHand; + previousHand = NULL; + + attached = false; + } + + // If we were the next hand, clear it + if ( player->handNext == this ) { + player->handNext = NULL; + } + + owner = NULL; + + return( true ); +} + +/* +============ +hhHand::Event_RemoveHand + Plays the animations and schedules pointer changes +============ +*/ +void hhHand::Event_RemoveHand( void ) { + RemoveHand(); +} + +/* +=========== +hhHand::AddHand +=========== +*/ +hhHand *hhHand::AddHand( hhPlayer *player, const char *handclass, bool attachNow ) { + idDict args; + hhHand *hand = NULL; + + args.SetVector( "origin", player->GetEyePosition() ); + args.SetMatrix( "rotation", player->viewAngles.ToMat3() ); + + hand = static_cast< hhHand * >( gameLocal.SpawnObject( handclass, &args) ); + if ( hand ) { + hand->SetOwner( player ); + // We have a prob if you can't attach. Should return NULL + if ( !hand->AttachHand( player, attachNow ) ) { + hand->SetOwner( NULL ); + + gameLocal.hands.Remove( hand ); + + hand->PostEventMS( &EV_Remove, 0 ); + + return( NULL ); + } + } + + return( hand ); +} + + +/* +============ +hhHand::PrintHandInfo +============ +*/ +void hhHand::PrintHandInfo( idPlayer *player ) { + hhPlayer *hhplayer = NULL; + hhHand *hand = NULL; + int count = 0; + idList< idEntityPtr< hhHand > > orphaned; + + if ( player->IsType( hhPlayer::Type ) ) { + hhplayer = (hhPlayer *) player; + } + else { + return; + } + + orphaned = gameLocal.hands; + + gameLocal.Printf( "Weapon: %p Class: %s State: %d\n", player->weapon, + (const char *) player->weapon->GetDict()->GetString("classname"), + (int) player->weapon->GetStatus() ); + + hand = hhplayer->hand.GetEntity(); + while ( hand != NULL ) { + count++; + gameLocal.Printf( "Hand %d: %p Class: %s State: %d\n", count, hand, + hand->spawnArgs.GetString("classname"), (int) hand->GetStatus() ); + orphaned.Remove( hand ); + hand = hand->previousHand; + } + + count = 0; + for ( int i = 0; i < orphaned.Num(); ++i ){ + count++; + hand = orphaned[ i ].GetEntity(); + gameLocal.Printf( "Orphaned Hand %d: %p Class: %s State: %d\n", count, hand, + hand->spawnArgs.GetString("classname"), (int) hand->GetStatus() ); + } +} + + +/* +================ +hhHand::GetMasterDefaultPosition +================ +*/ +void hhHand::GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idActor* actor = NULL; + idEntity* master = GetBindMaster(); + + if( master ) { + if( master->IsType(idActor::Type) ) { + actor = static_cast( master ); + actor->DetermineOwnerPosition( masterOrigin, masterAxis ); + + masterOrigin = actor->ApplyLandDeflect( masterOrigin, 1.1f ); + } else { + hhAnimatedEntity::GetMasterDefaultPosition( masterOrigin, masterAxis ); + } + } +} diff --git a/neo/prey/Prey/game_hand.h b/neo/prey/Prey/game_hand.h new file mode 100644 index 00000000..0c9d2bf8 --- /dev/null +++ b/neo/prey/Prey/game_hand.h @@ -0,0 +1,171 @@ + +#ifndef __PREY_GAME_HAND_H__ +#define __PREY_GAME_HAND_H__ + +// Forward declar +class hhPlayer; + +extern const idEventDef EV_Hand_DoRemove; +extern const idEventDef EV_Hand_DoAttach; +extern const idEventDef EV_Hand_Remove; +extern const idEventDef EV_Hand_Ready; +extern const idEventDef EV_Hand_Lowered; +extern const idEventDef EV_Hand_Raise; + +typedef enum { + HS_UNKNOWN, + HS_READY, + HS_LOWERING, + HS_LOWERED, + HS_RAISING +} handStatus_t; + + +class hhHand : public hhAnimatedEntity { + +public: + + CLASS_PROTOTYPE(hhHand); + + virtual ~hhHand(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + enum { + EVENT_REMOVEHAND = hhAnimatedEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + static hhHand * AddHand( hhPlayer *player, const char *classname, bool attachNow = false ); + + static void PrintHandInfo( idPlayer *player ); + + virtual void SetModel( const char *modelname ); + + virtual void Present(); + + virtual void Action( void ) { }; + virtual void SetAction( const char* str ) {}; //HUMANHEAD bjk + + int GetPriority( void ) { return( priority ); }; + + int GetStatus( ) { return( status ); }; + + void Reraise( ); + + bool IsReady() { return( status == HS_READY ); } + bool IsLowering() { return( status == HS_LOWERING || status == HS_LOWERED ); } + bool IsLowered() { return( status == HS_LOWERED ); } + bool IsRaising() { return( status == HS_RAISING ); } + + virtual void Raise(); + virtual void PutAway(); + virtual void Ready(); + + void Event_Ready(); + void Event_Lowered(); + void Event_Raise(); + + + void PlayAnim( int channel, const char *animName, const idEventDef *animEvent = NULL ); + void CycleAnim( int channel, const char *animname, int blendTime ); + + bool AttachHand( hhPlayer *player, bool attachNow = false ); + bool RemoveHand( void ); + + bool IsAttached() { return attached; } + + int LowerHandOrWeapon( hhPlayer *player ); + void RaiseHandOrWeapon( hhPlayer *player ); + int HandleWeapon( hhPlayer *player, hhHand *topHand, int weaponDelay = 0, bool doNow = false ); + + // Replace the player hand with this hand. Does a quick swap + void ReplaceHand( hhPlayer *player ); + + int GetAttachTime( ) { return( attachTime ); }; + + // Functions that could be put in common base class + int GetAnimDoneTime() { return( animDoneTime ); }; + + void SetOwner( idActor *owner ); + void GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + + virtual bool IsValidFor( hhPlayer *who ) { return( true ); } + + // These used to be protected. Call only if you know what you are doing! + hhHand * GetPreviousHand( hhPlayer *player = NULL ); + + void Event_DoHandAttach( idEntity *player ); + + void ForceRemove( void ); + +protected: + void Event_RemoveHand( void ); + + // Event only methods. Should NOT be called directly + void Event_DoHandRemove( void ); + + bool DoHandAttach( idEntity *player ); + bool DoHandRemove( void ); + + +protected: + bool CheckHandAttach( hhPlayer *player ); + bool CheckHandRemove( hhPlayer *player ); + +protected: + // Who we are associated with + idEntityPtr owner; + + // What is the priority of this hand? + int priority; + + // Previous hand. Will restore on closing down! + hhHand *previousHand; + + // Should we lower the weapon for this hand? + bool lowerWeapon; + + // If we don't lower the weapon, do we put it aside? + bool asideWeapon; + + // DEBUG - We should never delete w/out being attached + bool attached; + + // Should we replace the previous hand instead of putting it on the hand stack? + bool replacePrevious; + + // What time do we attach? + int attachTime; + + // What hands do we represent? (1 = right, 2 = left, 3 = both) + int handedness; + + // When will we be done animating? + int animDoneTime; + + // How many frames should we blend? + int animBlendFrames; + + // Status of the hand; + int status; + + // What state we would like to be + int idealState; + + // Last event posted with an anim + const idEventDef * animEvent; + + hhPhysics_StaticWeapon physicsObj; +}; + +#endif /* __PREY_GAME_HAND_H__ */ + diff --git a/neo/prey/Prey/game_handcontrol.cpp b/neo/prey/Prey/game_handcontrol.cpp new file mode 100644 index 00000000..a0ac5362 --- /dev/null +++ b/neo/prey/Prey/game_handcontrol.cpp @@ -0,0 +1,110 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION( hhHand, hhControlHand ) +END_CLASS + + +void hhControlHand::Spawn() { + bProcessControls = false; + oldStatus = 0; + + anims[ 0 ][ 0 ] = GetAnimator()->GetAnim("bottom_backward"); + anims[ 0 ][ 1 ] = GetAnimator()->GetAnim("bottom_center"); + anims[ 0 ][ 2 ] = GetAnimator()->GetAnim("bottom_forward"); + anims[ 1 ][ 0 ] = GetAnimator()->GetAnim("center_backward"); + anims[ 1 ][ 1 ] = GetAnimator()->GetAnim("center_center"); + anims[ 1 ][ 2 ] = GetAnimator()->GetAnim("center_forward"); + anims[ 2 ][ 0 ] = GetAnimator()->GetAnim("top_backward"); + anims[ 2 ][ 1 ] = GetAnimator()->GetAnim("top_center"); + anims[ 2 ][ 2 ] = GetAnimator()->GetAnim("top_forward"); + + fl.networkSync = true; +} + +void hhControlHand::Save(idSaveGame *savefile) const { + savefile->Write( anims, sizeof(int)*HAND_MATRIX_WIDTH*HAND_MATRIX_HEIGHT ); + savefile->WriteBool( bProcessControls ); + savefile->WriteInt( oldStatus ); +} + +void hhControlHand::Restore( idRestoreGame *savefile ) { + savefile->Read( anims, sizeof(int)*HAND_MATRIX_WIDTH*HAND_MATRIX_HEIGHT ); + savefile->ReadBool( bProcessControls ); + savefile->ReadInt( oldStatus ); +} + +void hhControlHand::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(bProcessControls, 1); + msg.WriteBits(oldStatus, 32); + + hhHand::WriteToSnapshot(msg); +} + +void hhControlHand::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bProcessControls = !!msg.ReadBits(1); + oldStatus = msg.ReadBits(32); + + hhHand::ReadFromSnapshot(msg); +} + +void hhControlHand::ClientPredictionThink( void ) { + RunPhysics(); + + // HUMANHEAD pdm + if (thinkFlags & TH_TICKER) { + Ticker(); + } + + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + + +void hhControlHand::Raise( void ) { + hhHand::Raise(); + + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, 1.0f); // Dir +} + +void hhControlHand::Ready() { + hhHand::Ready(); + bProcessControls = true; +} + +void hhControlHand::PutAway( void ) { + hhHand::PutAway(); + + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // Dir +} + +void hhControlHand::UpdateControlDirection(idVec3 &dir) { + int anim; + + int curStatus = dir.DirectionMask(); + + if (bProcessControls && oldStatus != curStatus) { + // Determine which anim group to play. (Up/Down/Normal & Forward/Center/Back) + int z_index = dir.z < 0 ? 0 : dir.z > 0 ? 2 : 1; + int x_index = dir.x < 0 ? 0 : dir.x > 0 ? 2 : 1; + + anim = anims[ z_index ][ x_index ]; + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 250); + + // Now determine which left and right to play + float left = dir.y > 0 ? 1.0f : 0.0f; + float right = dir.y < 0 ? 1.0f : 0.0f; + + GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( 0, left ); + GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( 1, 1.0f ); + GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->SetSyncedAnimWeight( 2, right ); + + oldStatus = curStatus; + } +} diff --git a/neo/prey/Prey/game_handcontrol.h b/neo/prey/Prey/game_handcontrol.h new file mode 100644 index 00000000..77b71974 --- /dev/null +++ b/neo/prey/Prey/game_handcontrol.h @@ -0,0 +1,34 @@ + +#ifndef __PREY_GAME_HAND_CONTROL_H__ +#define __PREY_GAME_HAND_CONTROL_H__ + +#define HAND_MATRIX_WIDTH 3 +#define HAND_MATRIX_HEIGHT 3 + +class hhControlHand : public hhHand { +public: + CLASS_PROTOTYPE(hhControlHand); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void UpdateControlDirection(idVec3 &dir); + virtual void Ready(); + virtual void Raise(); + virtual void PutAway(); + +protected: + // Matrix of anims for up/down/normal & left/ceneter/right + int anims[ HAND_MATRIX_WIDTH ][ HAND_MATRIX_HEIGHT ]; + + bool bProcessControls; + int oldStatus; +}; + +#endif diff --git a/neo/prey/Prey/game_healthbasin.cpp b/neo/prey/Prey/game_healthbasin.cpp new file mode 100644 index 00000000..e4cba581 --- /dev/null +++ b/neo/prey/Prey/game_healthbasin.cpp @@ -0,0 +1,246 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_GiveHealth( "giveHealth" ); + +const idEventDef EV_IdleMode( "" ); +const idEventDef EV_FinishedPuke( "" ); +const idEventDef EV_Expended( "" ); + + +CLASS_DECLARATION( hhAnimatedEntity, hhHealthBasin ) + EVENT( EV_Activate, hhHealthBasin::Event_Activate ) + EVENT( EV_GiveHealth, hhHealthBasin::Event_GiveHealth ) + EVENT( EV_Broadcast_AppendFxToList, hhHealthBasin::Event_AppendFxToIdleList ) + + EVENT( EV_IdleMode, hhHealthBasin::Event_IdleMode ) + EVENT( EV_FinishedPuke, hhHealthBasin::Event_FinishedPuking ) + EVENT( EV_Expended, hhHealthBasin::Event_Expended ) +END_CLASS + + +void hhHealthBasin::Event_IdleMode() { + PlayCycle( "idle" ); + StartSound( "snd_idle", SND_CHANNEL_IDLE ); + BasinMode = BASIN_Idle; +} + +void hhHealthBasin::Event_FinishedPuking() { + if( currentHealth <= 0.f ) { + Event_Expended(); + } + else { + PostEventMS( &EV_IdleMode, PlayAnim("transidle", 4) ); + } +} + +void hhHealthBasin::Event_Expended() { + PlayAnim( "transdeath" ); + StartSound( "snd_die", SND_CHANNEL_ANY ); + hhUtils::RemoveContents( idleFxList, true ); //MDC: This doesn't seem to be working, possibly something with the broadcast fx + BasinMode = BASIN_Expended; +} +/* +================ +hhHealthBasin::hhHealthBasin +================ +*/ +hhHealthBasin::hhHealthBasin() { +} + +/* +================ +hhHealthBasin::Spawn +================ +*/ +void hhHealthBasin::Spawn() { + if ( g_wicked.GetBool() ) { // CJR: Don't spawn health basins in wicked mode + PostEventMS( &EV_Remove, 0 ); + } + + currentHealth = spawnArgs.GetFloat( "maxHealth" ); + activator = NULL; + fl.takedamage = true; + + verificationAbsBounds.Clear(); + GetPhysics()->SetContents( CONTENTS_BODY ); + + SpawnTrigger(); + SpawnFx(); + + PostEventMS( &EV_IdleMode, 0 ); +} + +void hhHealthBasin::Save( idSaveGame *savefile ) const { + savefile->WriteInt( BasinMode ); + activator.Save( savefile ); + savefile->WriteFloat( currentHealth ); + savefile->WriteBounds( verificationAbsBounds ); + + int num = idleFxList.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Save( savefile ); + } +} + +void hhHealthBasin::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( reinterpret_cast ( BasinMode ) ); + activator.Restore( savefile ); + savefile->ReadFloat( currentHealth ); + savefile->ReadBounds( verificationAbsBounds ); + + int num; + savefile->ReadInt( num ); + idleFxList.Clear(); + idleFxList.SetNum( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Restore( savefile ); + } +} + +/* +================ +hhHealthBasin::~hhHealthBasin +================ +*/ +hhHealthBasin::~hhHealthBasin() { + hhUtils::RemoveContents( idleFxList, true ); +} + +/* +=============== +hhHealthBasin::SpawnTrigger +=============== +*/ +void hhHealthBasin::SpawnTrigger() { + idDict args; + + args.Set( "target", name.c_str() ); + args.Set( "mins", spawnArgs.GetString("triggerMins") ); + args.Set( "maxs", spawnArgs.GetString("triggerMaxs") ); + args.SetVector( "origin", GetOrigin() ); + args.SetMatrix( "rotation", GetAxis() ); + gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &args ); + + verificationAbsBounds.FromTransformedBounds( idBounds(spawnArgs.GetVector("triggerMins"), spawnArgs.GetVector("triggerMaxs") ) + idBounds( vec3_zero, idVec3(75.0f, 0.0f, 0.0f)), GetOrigin(), GetAxis() ); +} + +/* +=============== +hhHealthBasin::SpawnFx +=============== +*/ +void hhHealthBasin::SpawnFx() { + BroadcastFxInfoAlongBonePrefixUnique( &spawnArgs, "fx_idle", "joint_idleFx", NULL, &EV_Broadcast_AppendFxToList ); +} + +/* +=============== +hhHealthBasin::PlayAnim +=============== +*/ +int hhHealthBasin::PlayAnim( const char* pName, int iBlendTime ) { + int pAnim = 0; + + ClearAnims( iBlendTime ); + + if( !pName && !pName[0] ) { + return 0; + } + + pAnim = GetAnimator()->GetAnim( pName ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, pAnim, gameLocal.time, FRAME2MS( iBlendTime ) ); + + return (pAnim != NULL) ? GetAnimator()->GetAnim( pAnim )->Length() : 0; +} + +/* +=============== +hhHealthBasin::PlayCycle +=============== +*/ +void hhHealthBasin::PlayCycle( const char* pName, int iBlendTime ) { + ClearAnims( iBlendTime ); + + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim(pName), gameLocal.time, FRAME2MS( iBlendTime ) ); +} + +/* +=============== +hhHealthBasin::ClearAnims +=============== +*/ +void hhHealthBasin::ClearAnims( int iBlendTime ) { + GetAnimator()->ClearAllAnims( gameLocal.time, FRAME2MS( iBlendTime ) ); +} + +/* +=============== +hhHealthBasin::ActivatorVerified +=============== +*/ +bool hhHealthBasin::ActivatorVerified( const idEntityPtr& Activator ) { + if( !Activator.IsValid() || !Activator->GetPhysics()->GetAbsBounds().IntersectsBounds(verificationAbsBounds) ) { + return false; + } + //mdc - also check the health to be a valid activator + if( Activator->health < Activator->GetMaxHealth() ) { + return true; + } + return false; +} + +/* +=============== +hhHealthBasin::Event_AppendFxToIdleList +=============== +*/ +void hhHealthBasin::Event_AppendFxToIdleList( hhEntityFx* fx ) { + idleFxList.Append( fx ); +} + +/* +=============== +hhHealthBasin::Event_Activate +=============== +*/ +void hhHealthBasin::Event_Activate( idEntity *activatedby ) { + if( BasinMode == BASIN_Idle ) { //only allow activation when idle + activator = static_cast(activatedby); + if( ActivatorVerified(activator) ) { //mdc: only begin puking if valid activator (including less than nominal health) + BasinMode = BASIN_Puking; + StopSound( SND_CHANNEL_IDLE ); + StartSound( "snd_puke", SND_CHANNEL_ANY ); + PostEventMS( &EV_FinishedPuke, PlayAnim("puke") ); + } + } +} + +/* +=============== +hhHealthBasin::Event_GiveHealth + +Called from frame command +=============== +*/ +void hhHealthBasin::Event_GiveHealth() { + float amountApplied = 0.0f; + + assert( currentHealth > 0.0f ); + + //Make sure activator is still in the trigger volume, and that they still need health.. + if( !ActivatorVerified(activator) ) { + return; + } + + int oldHealth = activator->health; + if( activator->Give("health", va("%.2f", currentHealth)) ) { + amountApplied = activator->health - oldHealth; + currentHealth -= amountApplied; + + ActivateTargets( activator.GetEntity() ); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/game_healthbasin.h b/neo/prey/Prey/game_healthbasin.h new file mode 100644 index 00000000..83dc0c33 --- /dev/null +++ b/neo/prey/Prey/game_healthbasin.h @@ -0,0 +1,54 @@ +#ifndef __HH_HEALTH_BASIN_H +#define __HH_HEALTH_BASIN_H + +/* +TODO: + + effects do not delete when using hhUtils::RemoveContents + + Need to have player face basin to allow use +*/ + +class hhHealthBasin : public hhAnimatedEntity { + CLASS_PROTOTYPE( hhHealthBasin ); + +public: + hhHealthBasin(); + virtual ~hhHealthBasin(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SpawnTrigger(); + void SpawnFx(); + + bool ActivatorVerified( const idEntityPtr& Activator ); + + int PlayAnim( const char* pName, int iBlendTime = 0 ); + void PlayCycle( const char* pName, int iBlendTime = 0 ); + void ClearAnims( int iBlendTime = 0 ); + +protected: + void Event_AppendFxToIdleList( hhEntityFx* fx ); + void Event_Activate( idEntity *pActivator ); + void Event_IdleMode(); + void Event_FinishedPuking(); + void Event_Expended(); + void Event_GiveHealth(); + +protected: + enum EBasinMode { + BASIN_Idle, + BASIN_Puking, + BASIN_Expended, + } BasinMode; + + idEntityPtr activator; + float currentHealth; + + idBounds verificationAbsBounds; + + idList< idEntityPtr > idleFxList; +}; + +#endif diff --git a/neo/prey/Prey/game_healthspore.cpp b/neo/prey/Prey/game_healthspore.cpp new file mode 100644 index 00000000..bffe40fe --- /dev/null +++ b/neo/prey/Prey/game_healthspore.cpp @@ -0,0 +1,121 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define DEFAULT_SPORE_RESPAWN 15000 + +const idEventDef EV_RespawnSpore( "" ); + +CLASS_DECLARATION( idEntity, hhHealthSpore ) + EVENT( EV_Touch, hhHealthSpore::Event_Touch ) + EVENT( EV_RespawnSpore, hhHealthSpore::Event_RespawnSpore ) +END_CLASS + +/* +================ +hhHealthSpore::Spawn +================ +*/ +void hhHealthSpore::Spawn() { + if ( g_wicked.GetBool() ) { // CJR: Don't spawn health spores in wicked mode + PostEventMS( &EV_Remove, 0 ); + } + + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + SetShaderParm( SHADERPARM_MODE, 1.0f ); // Enable the additive glow on the spore + + fl.networkSync = true; +} + +/* +================ +hhHealthSpore::Event_RespawnSpore +================ +*/ +void hhHealthSpore::Event_RespawnSpore() { + if (gameLocal.isClient) { + return; + } + + GetPhysics()->SetContents( CONTENTS_TRIGGER ); //restore contents + SetShaderParm( SHADERPARM_MODE, 1.0f ); //restore additive pass +} + +void hhHealthSpore::Save(idSaveGame *savefile) const { +} + +void hhHealthSpore::Restore( idRestoreGame *savefile ) { +} + +void hhHealthSpore::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); +} + +void hhHealthSpore::ReadFromSnapshot( const idBitMsgDelta &msg ) { + renderEntity.shaderParms[SHADERPARM_MODE] = msg.ReadFloat(); + UpdateVisuals(); +} + +/* +================ +hhHealthSpore::ApplyEffect +================ +*/ +void hhHealthSpore::ApplyEffect( idActor* pActor ) { + if( pActor ) { + int oldHealth = pActor->health; + const char *itemHealthKey = "health"; + if (gameLocal.isMultiplayer) { //rww + itemHealthKey = "health_mp"; + } + pActor->Give( "health", spawnArgs.GetString(itemHealthKey) ); + } +} + +/* +================ +hhHealthSpore::Event_Touch +================ +*/ +void hhHealthSpore::Event_Touch( idEntity* pOther, trace_t* pTraceInfo ) { + hhFxInfo fxInfo; + idActor* pActor = NULL; + + if( pOther && pOther->IsType(idActor::Type) ) { + pActor = static_cast( pOther ); + + //rww - do not go above 100 in mp, even when maxhealth has been raised + if (gameLocal.isMultiplayer && pActor->IsType(hhPlayer::Type) && pActor->health >= MAX_HEALTH_NORMAL_MP) { + return; + } + + if( !pActor->IsDamaged() || pActor->health <= 0 ) { + return; + } + } + + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", GetOrigin(), GetAxis(), &fxInfo ); + + SetShaderParm( SHADERPARM_MODE, 0.0f ); // Disable the additive glow on the spore + + ApplyEffect( pActor ); + + ActivateTargets( pActor ); + + //rww - broadcast + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, true ); + + GetPhysics()->SetContents( 0 ); //MDC - clear our contents, so we can not get re-touched. + fl.refreshReactions = false; // JRM - no since telling AI anymore + + if (gameLocal.isMultiplayer) { //rww - respawn functionality for mp + int respawnTime; + if (!spawnArgs.GetInt("respawn", "0", respawnTime)) { + respawnTime = DEFAULT_SPORE_RESPAWN; + } + PostEventMS(&EV_RespawnSpore, respawnTime); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/game_healthspore.h b/neo/prey/Prey/game_healthspore.h new file mode 100644 index 00000000..c1638a77 --- /dev/null +++ b/neo/prey/Prey/game_healthspore.h @@ -0,0 +1,23 @@ +#ifndef __HH_HEALTH_SPORE_H +#define __HH_HEALTH_SPORE_H + +class hhHealthSpore : public idEntity { + CLASS_PROTOTYPE( hhHealthSpore ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + void ApplyEffect( idActor* pActor ); + + void Event_Touch( idEntity* pOther, trace_t* pTraceInfo ); + virtual void Event_RespawnSpore(); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_inventory.cpp b/neo/prey/Prey/game_inventory.cpp new file mode 100644 index 00000000..06aee388 --- /dev/null +++ b/neo/prey/Prey/game_inventory.cpp @@ -0,0 +1,579 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +void hhInventory::Clear() { + memset( &requirements, 0, sizeof( requirements ) ); + maxSpirit = 0; + bHasDeathwalked = false; + storedHealth = 0; + energyType = "energy_plasma"; + memset( altMode, 0, sizeof( altMode ) ); + memset( weaponRaised, 0, sizeof( weaponRaised ) ); + memset( lastShot, 0, sizeof( lastShot ) ); //HUMANHEAD bjk PATCH 7-27-06 + zoomFov = 0; + + idInventory::Clear(); +} + +void hhInventory::Save(idSaveGame *savefile) const { + idInventory::Save( savefile ); + + savefile->WriteBool(bHasDeathwalked); + savefile->Write(&maxSpirit, sizeof(maxSpirit)); + savefile->Write(&requirements, sizeof(requirements)); + savefile->WriteInt( storedHealth ); + savefile->WriteString( energyType.c_str() ); + savefile->Write(&altMode, sizeof(altMode)); + savefile->Write(&weaponRaised, sizeof(weaponRaised)); + savefile->WriteInt( zoomFov ); +} + +void hhInventory::Restore( idRestoreGame *savefile ) { + idInventory::Restore( savefile ); + + savefile->ReadBool(bHasDeathwalked); + savefile->Read(&maxSpirit, sizeof(maxSpirit)); + savefile->Read(&requirements, sizeof(requirements)); + savefile->ReadInt( storedHealth ); + savefile->ReadString( energyType ); + savefile->Read(&altMode, sizeof(altMode)); + savefile->Read(&weaponRaised, sizeof(weaponRaised)); + memset( lastShot, 0, sizeof( lastShot ) ); //HUMANHEAD bjk PATCH 7-27-06 + savefile->ReadInt( zoomFov ); +} + +/* +============== +hhInventory::GetPersistantData + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhInventory::GetPersistantData( idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + const idKeyValue *kv; + const char *name; + + // don't bother with powerups or the clip + + // maxhealth, maxspirit + dict.SetInt( "maxhealth", maxHealth); + dict.SetInt( "max_ammo_spiritpower", maxSpirit); + dict.SetBool( "bHasDeathwalked", bHasDeathwalked ); + dict.SetInt( "storedHealth", storedHealth ); + dict.Set( "energyType", energyType.c_str() ); //HUMANHEAD bjk + dict.SetInt( "zoomFov", zoomFov ); //HUMANHEAD bjk + + // ammo + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); + if ( name ) { + dict.SetInt( name, ammo[ i ] ); + } + } + + //HUMANHEAD bjk: weapons + for( i = 0; i < MAX_WEAPONS; i++ ) { + sprintf( key, "altMode_%i", i ); + dict.SetBool( key, altMode[i] ); + sprintf( key, "weaponRaised_%i", i ); + dict.SetBool( key, weaponRaised[i] ); + } + //HUMANHEAD END + + // 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 ); + } + + // HUMANHEAD CJR: copy all keys with "def_" + // Needed for Hunter Hand GUI + kv = item->MatchPrefix( "def_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "def_", kv ); + } + } // HUMANHEAD END + + // HUMANHEAD CJR: copy all keys with "passtogui_" + // Needed for Hunter Hand GUI + kv = item->MatchPrefix( "passtogui_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "passtogui_", kv ); + } + } // HUMANHEAD END + + num++; + } + } + dict.SetInt( "items", num ); + + // weapons + dict.SetInt( "weapon_bits", weapons ); + + 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 ); + } +} + + +/* +============== +hhInventory::RestoreInventory + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + idStr itemname; + const idKeyValue *kv; + const char *name; + + Clear(); + + // health + maxHealth = dict.GetInt( "maxhealth", "100" ); + bHasDeathwalked = dict.GetBool( "bHasDeathwalked" ); + storedHealth = dict.GetInt( "storedHealth", "0" ); + energyType = dict.GetString( "energyType", "energy_plasma" ); + zoomFov = dict.GetInt( "zoomFov" ); + + // the clip and powerups aren't restored + + // max spirit + maxSpirit = dict.GetInt( "max_ammo_spiritpower" ); + + // ammo + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); + if ( name ) { + ammo[ i ] = dict.GetInt( name ); + } + } + + //HUMANHEAD bjk: weapons + for( i = 0; i < MAX_WEAPONS; i++ ) { + sprintf( key, "altMode_%i", i ); + altMode[i] = dict.GetBool( key ); + sprintf( key, "weaponRaised_%i", i ); + weaponRaised[i] = dict.GetBool( key ); + } + //HUMANHEAD END + + // 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 ); + } + } + + //HUMANHEAD aob: in addition to the persistent items, give hardcoded items from players.def + Give( owner, dict, "item", dict.GetString( "item" ), NULL, true ); + //HUMANHEAD END + + // weapons are stored as a number for persistant data, but as strings in the entityDef + weapons = dict.GetInt( "weapon_bits", "0" ); + Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false ); + + 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 ); + } + + // Keep weapon switch HUD element from showing up at level load + weaponPulse = false; +} + +int hhInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const { + int max = 0; + if (ammo_classname != NULL) { + if (!idStr::Icmp(ammo_classname, "ammo_spiritpower")) { + max = maxSpirit; + } + else { + max = idInventory::MaxAmmoForAmmoClass(owner, ammo_classname); + } + } + return max; +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhInventory::AddPickupName( const char *name, const char *icon, bool bIsWeapon) { + if ( idStr::Length(icon) > 0 ) { + idItemInfo &info = pickupItemNames.Alloc(); + + if ( !idStr::Icmpn( name, STRTABLE_ID, strlen( STRTABLE_ID ) ) ) { + info.name = common->GetLanguageDict()->GetString( name ); + } else { + info.name = name; + } + info.icon = icon; + info.time = 0; + info.slotZeroTime = 0; + info.matcolorAlpha = 0.0f; + info.bDoubleWide = bIsWeapon; + } +} + +/* +============== +hhInventory::Give + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +bool hhInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud ) { + int i; + const char *pos; + const char *end; + int len; + idStr weaponString; + int max; + const idDeclEntityDef *weaponDecl; + bool tookWeapon; + int amount; + idItemInfo info; + hhPlayer* playerOwner; + + + if( owner && owner->IsType( hhPlayer::Type ) ) { + playerOwner = static_cast(owner); + } + + if ( !idStr::Icmp( statname, "health" ) || !idStr::Icmp( statname, "healthspecial" ) ) { //healthspecial is for mp and indicates that this item can push a player's health up to the "real" maxhealth + int localMaxHealth = maxHealth; + if (gameLocal.isMultiplayer) { //rww - only pipes can put us above 100 in mp + if (idStr::Icmp( statname, "healthspecial" )) { + localMaxHealth = MAX_HEALTH_NORMAL_MP; + } + } + if ( playerOwner->health >= localMaxHealth ) { + return false; + } + int oldHealth = playerOwner->health; + playerOwner->health += atoi( value ); + if ( playerOwner->health > localMaxHealth ) { + playerOwner->health = localMaxHealth; + } + if (playerOwner) { + playerOwner->healthPulse = true; + } + } else if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + if ( ammo[ i ] >= max ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + ammoPulse = true; + } + if (playerOwner && !idStr::Icmp(statname, "ammo_spiritpower")) { + playerOwner->spiritPulse = true; + } + } else if ( !idStr::Icmp( statname, "item" ) ) { + pos = value; + while( pos != NULL ) { + end = strchr( pos, ',' ); + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr itemName( pos, 0, len ); + + GiveItem( spawnArgs, gameLocal.FindEntityDefDict(itemName.c_str(), false) ); + + pos = end; + } + } else if ( !idStr::Icmp( statname, "weapon" ) ) { + 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 name + for( i = 1; i < MAX_WEAPONS; i++ ) { + if ( weaponName == playerOwner->GetWeaponName(i) ) { + break; + } + } + + if ( i >= MAX_WEAPONS ) { + gameLocal.Error( "Unknown weapon '%s'", weaponName.c_str() ); + } + + // cache the media for this weapon + weaponDecl = gameLocal.FindEntityDef( weaponName, false ); + + // don't pickup "no ammo" weapon types twice + // not for D3 SP .. 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 conistent to pick it up + if ( gameLocal.isMultiplayer && weaponDecl && ( weapons & ( 1 << i ) ) && !weaponDecl->dict.GetInt( "ammoRequired" ) ) { + continue; + } + + if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weaponobj_fists" ) ) { + playerOwner->UnlockWeapon( i ); //TODO add key for disabling this + if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) { + if ( (owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) || !gameLocal.isMultiplayer) && idealWeapon ) { + // HUMANHEAD pdm: added spirit check, so we don't autoswitch to weapons when picking them up in spriitwalk + // HUMANHEAD pdm: also added check for spiritweapon, don't autoswitch to it. + if (!static_cast(owner)->IsSpiritOrDeathwalking() && weaponName.Icmp("weaponobj_bow")) { + assert( !gameLocal.isClient ); + *idealWeapon = i; + } + } + + // Pulse if not picking up the spirit bow + if (weaponName.Icmp("weaponobj_bow") != 0) { + weaponPulse = true; + } + weapons |= ( 1 << i ); + tookWeapon = true; + } + } + } + return tookWeapon; + } + else if ( !idStr::Icmp( statname, "maxhealth" ) ) { + if (owner) { + if (gameLocal.GetLocalPlayer() == owner) { //sp, listen server + if (owner->hud) { + owner->hud->HandleNamedEvent( "maxHealthPulse" ); + } + } + else if (gameLocal.isMultiplayer && !gameLocal.isClient) { //otherwise, broadcast event to owner + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits((1<ServerSendEvent(idPlayer::EVENT_MENUEVENT, &msg, false, -1, owner->entityNumber); + } + } + if (gameLocal.isMultiplayer) { //rww - different behaviour for mp + maxHealth = atoi(value); + } + else { + maxHealth += atoi(value); + } + } + else if ( !idStr::Icmp( statname, "maxspirit" ) ) { + owner->hud->HandleNamedEvent( "maxSpiritPulse" ); + maxSpirit += atoi(value); + } + else { + // unknown item + return false; + } + + return true; +} + +/* +============== +hhInventory::GiveItem +============== +*/ +bool hhInventory::GiveItem( const idDict& spawnArgs, const idDict* item ) { + idStr itemName; + + if( !item ) { + return false; + } + + idDict* dict = new idDict( *item ); + + if( !FindItem(item) ) { + items.Append( dict ); + return true; + } + + SAFE_DELETE_PTR( dict ); + return false; +} + +/* +=============== +hhInventory::HasAmmo +=============== +*/ +int hhInventory::HasAmmo( ammo_t type, int amount ) { + assert(type >= 0 && type < AMMO_NUMTYPES); + + if ( ( type == idWeapon::GetAmmoNumForName("ammo_none") ) || !amount ) { + // always allow weapons that don't use ammo to fire + return -1; + } + + // check if we have infinite ammo + if ( ammo[ type ] < 0 ) { + return -1; + } + + // return how many shots we can fire + return ammo[ type ] / amount; +} + +/* +=============== +hhInventory::HasAmmo +=============== +*/ +int hhInventory::HasAmmo( const char *weapon_classname ) { + int ammoRequired; + ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( ammo_i, ammoRequired ); +} + +/* +=============== +hhInventory::HasAltAmmo +HUMANHEAD bjk +=============== +*/ +int hhInventory::HasAltAmmo( const char *weapon_classname ) { + int ammoRequired; + ammo_t ammo_i = AltAmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( ammo_i, ammoRequired ); +} + +/* +=============== +hhInventory::UseAmmo +=============== +*/ +bool hhInventory::UseAmmo( ammo_t type, int amount ) { + if ( !HasAmmo( type, amount ) ) { + return false; + } + + // take an ammo away if not infinite + if ( ammo[ type ] >= 0 ) { + ammo[ type ] -= amount; + //rww - don't forget this, it's important! + ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + } + + return true; +} + +float hhInventory::AmmoPercentage(idPlayer *player, ammo_t type) { + float amount = ammo[type]; + const char *ammoName = idWeapon::GetAmmoNameForNum( type ); + float max = MaxAmmoForAmmoClass( player, ammoName ); + max = max(1, max); + return amount / max; +} + +/* +=============== +hhInventory::FindInventoryItem +=============== +*/ +idDict* hhInventory::FindItem( const idDict* dict ) { + if( !dict ) { + return NULL; + } + + return FindItem( dict->GetString("inv_name") ); +} + +/* +=============== +hhInventory::FindInventoryItem +=============== +*/ +idDict* hhInventory::FindItem( const char *name ) { + const char* lname = NULL; + + for( int ix = 0; ix < items.Num(); ++ix ) { + if( !items[ix] ) { + continue; + } + + lname = items[ix]->GetString( "inv_name" ); + if ( lname && *lname ) { + if ( idStr::Icmp( name, lname ) == 0 ) { + return items[ix]; + } + } + } + return NULL; +} + +/* +=============== +hhInventory::EvaluateRequirements +=============== +*/ +void hhInventory::EvaluateRequirements(idPlayer *p) { + if (p) { + requirements.bCanDeathWalk = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_deathwalk"), 0); + requirements.bCanSpiritWalk = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_spiritwalk"), 0); + requirements.bCanSummonTalon = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_talon"), 0); + requirements.bCanUseBowVision = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_bowvision"), 0); + requirements.bCanUseLighter = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_lighter"), 0); + requirements.bCanWallwalk = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_wallwalk"), 0); + requirements.bHunterHand = gameLocal.RequirementMet(p, p->spawnArgs.GetString("requirement_hunterhand"), 0); + + // See if talon should be spawned + if (p->IsType(hhPlayer::Type)) { + static_cast(p)->TrySpawnTalon(); + } + } +} + diff --git a/neo/prey/Prey/game_inventory.h b/neo/prey/Prey/game_inventory.h new file mode 100644 index 00000000..1b91af13 --- /dev/null +++ b/neo/prey/Prey/game_inventory.h @@ -0,0 +1,54 @@ + +#ifndef __PREY_GAME_INVENTORY_H__ +#define __PREY_GAME_INVENTORY_H__ + + +class hhInventory : public idInventory { + +public: + virtual void GetPersistantData( idDict &dict ); + virtual void RestoreInventory( idPlayer *owner, const idDict &dict ); + virtual bool Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud ); + virtual void Clear(); + virtual int MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool GiveItem( const idDict& spawnArgs, const idDict* item ); + int HasAmmo( ammo_t type, int amount ); + bool UseAmmo( ammo_t type, int amount ); + int HasAmmo( const char *weapon_classname ); + int HasAltAmmo( const char *weapon_classname ); + float AmmoPercentage(idPlayer *player, ammo_t ammoType); + + idDict* FindItem( const idDict* dict ); + idDict* FindItem( const char *name ); + void EvaluateRequirements( idPlayer *p ); + void AddPickupName( const char *name, const char *icon, bool bIsWeapon ); + + // Requirements are precomputed after any inventory item change, for faster access + struct { + bool bCanWallwalk : 1; + bool bCanSpiritWalk : 1; + bool bCanSummonTalon : 1; + bool bCanUseBowVision : 1; + bool bCanUseLighter : 1; + bool bHunterHand : 1; + bool bCanDeathWalk : 1; + } requirements; + int maxSpirit; + bool bHasDeathwalked; + int storedHealth; + + //bjk: persistent weapons + idStr energyType; + bool altMode[ MAX_WEAPONS ]; + bool weaponRaised[ MAX_WEAPONS ]; + int lastShot[ MAX_WEAPONS ]; //HUMANHEAD bjk PATCH 7-27-06 + int zoomFov; +}; + + +#endif /* __PREY_INVENTORY_H__ */ + diff --git a/neo/prey/Prey/game_itemautomatic.cpp b/neo/prey/Prey/game_itemautomatic.cpp new file mode 100644 index 00000000..f21a7582 --- /dev/null +++ b/neo/prey/Prey/game_itemautomatic.cpp @@ -0,0 +1,227 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + +hhItemAutomatic + +***********************************************************************/ + +CLASS_DECLARATION( idEntity, hhItemAutomatic ) +END_CLASS + + +//============================================================================= +// +// hhItemAutomatic::Spawn +// +//============================================================================= +void hhItemAutomatic::Spawn() { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + BecomeActive( TH_THINK ); +} + +//============================================================================= +// +// hhItemAutomatic::Think +// +//============================================================================= + +void hhItemAutomatic::Think() { + if ( thinkFlags & TH_THINK ) { // Guard against the first level-load think + // We only think when we are in the PVS. We think once and remove ourselves. + hhPlayer *player; + player = static_cast< hhPlayer *>( gameLocal.GetLocalPlayer() ); + if ( player && player->CheckFOV( this->GetOrigin() ) ) { + SpawnItem(); + } + } +} + +//============================================================================= +// +// hhItemAutomatic::SpawnItem +// +//============================================================================= + +void hhItemAutomatic::SpawnItem() { + hhPlayer *player; + idDict Args; + idStr itemName = NULL; + idEntity *spawned = NULL; + + // Remove this object and spawn in a new item in it's place + PostEventMS( &EV_Remove, 0 ); + + Args.SetBool( "spin", 0 ); + Args.SetFloat( "triggersize", 48.0f ); + Args.SetFloat( "respawn", 0.0f ); + Args.SetBool( "enablePickup", false ); + Args.SetFloat( "wander_radius", 12.0f ); // in case the item spawned is a crawler, don't let it wander very far + Args.Set( "target", spawnArgs.GetString( "target" ) ); // Pass the target on to the spawned item + + player = static_cast(gameLocal.GetLocalPlayer()); + + itemName = GetNewItem(); + + if ( itemName.IsEmpty() ) { + return; + } + + spawned = gameLocal.SpawnObject( itemName.c_str(), &Args ); + if ( spawned ) { + spawned->SetOrigin( this->GetOrigin() ); + spawned->SetAxis( this->GetAxis() ); + + if ( spawned->IsType( hhItem::Type ) ) { + hhItem *item = static_cast( spawned ); + item->EnablePickup(); + } + } +} + +//============================================================================= +// +// hhItemAutomatic::GetNewItem +// +//============================================================================= + +idStr hhItemAutomatic::GetNewItem() { + int i; + idStr defaultName; + float skipPercent; + int numAmmo; + + idList weaponNames; + idList weaponIndexes; + idList validWeapon; + idList ammoPercent; + idList ammoTypes; + idList itemNames; + idList skipPercentString; + + float total; + + bool bDontSkip = spawnArgs.GetBool( "bDontSkip", "0" ); + + // This is the heart of the DDA system: + // list all available weapons that can have ammo in the cabinet and how much ammo the player has + + hhPlayer *player = static_cast(gameLocal.GetLocalPlayer()); + + // Build lists of all valid weapon names, ammo types and ammo names + // Note that all these must line up -- so the first weapon corresponds with the first ammo type and the first item name + hhUtils::SplitString( idStr(spawnArgs.GetString( "weaponNames" )), weaponNames ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "ammoTypes" )), ammoTypes ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "itemNames" )), itemNames ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "skipPercents" )), skipPercentString ); + + numAmmo = weaponNames.Num(); + + if ( skipPercentString.Num() != numAmmo ) { + gameLocal.Error( "hhItemAutomatic::GetNewItem: skipPercent.Num() != weaponNames.Num()\n" ); + } + + weaponIndexes.SetNum( numAmmo ); + validWeapon.SetNum( numAmmo ); + ammoPercent.SetNum( numAmmo ); + + for( i = 0; i < weaponNames.Num(); i++ ) { + weaponIndexes[i] = player->GetWeaponNum( weaponNames[i].c_str() ); + } + + // compute percentages of each ammo compared to the max allowed + for( i = 0; i < numAmmo; i++ ) { + validWeapon[i] = false; + if ( player->inventory.weapons & (1 << weaponIndexes[i] ) ) { + int ammoIndex = player->inventory.AmmoIndexForAmmoClass( ammoTypes[i].c_str() ); + ammoPercent[i] = player->inventory.AmmoPercentage( player, ammoIndex ); + + if ( !bDontSkip ) { // Facility to guarantee that ammo won't be skipped + float adjustFactor = FindAmmoNearby( itemNames[i].c_str() ); // If similar ammo is nearby, then reduce the chance of this spawning that ammo + ammoPercent[i] *= adjustFactor; + + skipPercent = idMath::ClampFloat( 0.0f, 1.0f, (float)atof( skipPercentString[i].c_str() ) ); + skipPercent *= (1.0f - (gameLocal.GetDDAValue() * 0.5f + 0.25f)); // CJR TEST: Scale based upon DDA + + if ( ammoPercent[i] > skipPercent ) { // Skip this weapon if the player weapon percentage is high enough + validWeapon[i] = false; + continue; + } + } + + ammoPercent[i] = 1.0f - ammoPercent[i]; // Reverse it to make the math below a bit simpler + + if ( ammoPercent[i] == 0.0f ) { + ammoPercent[i] = 0.1f; // Give full ammo at least a slight chance + } + + validWeapon[i] = true; + } + } + + // re-compute the total of all percentages + total = 0; + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + total += ammoPercent[i]; + } + } + + // random number from 0 - total + float random = gameLocal.random.RandomFloat() * total; + + // calculate which ammo that number is associated with and add that item to the cabinet + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + if ( random <= ammoPercent[i] ) { // This is the ammo we want + return itemNames[i]; + } + + random -= ammoPercent[i]; // Not the item, so remove this percent and check the next value + } + } + + return NULL; +} + +//============================================================================= +// +// hhItemAutomatic::FindAmmoNearby +// +//============================================================================= + +float hhItemAutomatic::FindAmmoNearby( const char *ammoName ) { + int i; + int e; + hhItem *ent; + idEntity *entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 org; + float adjustFactor = 1.0f; + + float nearbySize = spawnArgs.GetFloat( "nearbySize", "512" ); + + org = GetPhysics()->GetOrigin(); + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = org[i] - nearbySize; + bounds[1][i] = org[i] + nearbySize; + } + + // Find the closest ammo types that are the same class + numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) { + ent = static_cast< hhItem * >( entityList[e] ); + + const char *name = ent->spawnArgs.GetString( "classname" ); + if ( !idStr::Icmp( name, ammoName ) ) { + adjustFactor += spawnArgs.GetFloat( "nearbyReduction", "0.25" ); + } + } + + return adjustFactor; +} \ No newline at end of file diff --git a/neo/prey/Prey/game_itemautomatic.h b/neo/prey/Prey/game_itemautomatic.h new file mode 100644 index 00000000..c920acaf --- /dev/null +++ b/neo/prey/Prey/game_itemautomatic.h @@ -0,0 +1,24 @@ +#ifndef __HH_ITEM_AUTOMATIC_H +#define __HH_ITEM_AUTOMATIC_H + +/*********************************************************************** + +hhItemAutomatic + +***********************************************************************/ + +class hhItemAutomatic : public idEntity { + CLASS_PROTOTYPE( hhItemAutomatic ); + +public: + void Spawn(); + virtual void Think(); + + +protected: + void SpawnItem(); + idStr GetNewItem(); + float FindAmmoNearby( const char *ammoName ); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_itemcabinet.cpp b/neo/prey/Prey/game_itemcabinet.cpp new file mode 100644 index 00000000..7c2b727f --- /dev/null +++ b/neo/prey/Prey/game_itemcabinet.cpp @@ -0,0 +1,417 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_GiveItems("", "e"); + +/*********************************************************************** + +hhItemCabinet + +***********************************************************************/ + +const idEventDef EV_EnableItemClip( "" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhItemCabinet ) + EVENT( EV_Activate, hhItemCabinet::Event_Activate ) + EVENT( EV_EnableItemClip, hhItemCabinet::Event_EnableItemClip ) + EVENT( EV_Broadcast_AppendFxToList, hhItemCabinet::Event_AppendFxToIdleList ) + EVENT( EV_PostSpawn, hhItemCabinet::Event_PostSpawn ) //rww +END_CLASS + +/* +================ +hhItemCabinet::hhItemCabinet +================ +*/ +hhItemCabinet::hhItemCabinet() { +} + +/* +================ +hhItemCabinet::Spawn +================ +*/ +void hhItemCabinet::Spawn() { + animDoneTime = 0; + + GetPhysics()->SetContents( CONTENTS_BODY ); + + ResetItemList(); + + InitBoneInfo(); + + if (!gameLocal.isClient) { + PostEventMS(&EV_PostSpawn, 0); + } + + StartSound( "snd_idle", SND_CHANNEL_IDLE ); +} + +/* +================ +hhItemCabinet::Event_PostSpawn +================ +*/ +void hhItemCabinet::Event_PostSpawn(void) { + SpawnIdleFX(); +} + +/* +================ +hhItemCabinet::~hhItemCabinet +================ +*/ +hhItemCabinet::~hhItemCabinet() { + + for( int i = 0; i < CABINET_MAX_ITEMS; i++) { + SAFE_REMOVE( itemList[i] ); + } + + hhUtils::RemoveContents< idEntityPtr >( idleFxList, true ); + + StopSound( SND_CHANNEL_IDLE ); +} + +/* +================ +hhItemCabinet::InitBoneInfo +================ +*/ +void hhItemCabinet::InitBoneInfo() { + boneList[0] = idStr( spawnArgs.GetString("bone_shelfTop") ); + boneList[1] = idStr(spawnArgs.GetString("bone_shelfMiddle") ); + boneList[2] = idStr(spawnArgs.GetString("bone_shelfBottom") ); +} + +void hhItemCabinet::Save(idSaveGame *savefile) const { + savefile->WriteInt( animDoneTime ); + + int num = idleFxList.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Save( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + itemList[i].Save( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + savefile->WriteString( boneList[i] ); + } +} + +void hhItemCabinet::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( animDoneTime ); + + int num; + savefile->ReadInt( num ); + idleFxList.Clear(); + idleFxList.SetNum( num ); + for( int i = 0; i < num; i++ ) { + idleFxList[i].Restore( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + itemList[i].Restore( savefile ); + } + + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + savefile->ReadString( boneList[i] ); + } +} + +/* +================ +hhItemCabinet::PlayAnim +================ +*/ +void hhItemCabinet::PlayAnim( const char* pAnimName, int iBlendTime ) { + PlayAnim( GetAnimator()->GetAnim(pAnimName), iBlendTime ); +} + +/* +================ +hhItemCabinet::PlayAnim +================ +*/ +void hhItemCabinet::PlayAnim( int pAnim, int iBlendTime ) { + int iAnimLength = 0; + + if( pAnim ) { + animator.ClearAllAnims( gameLocal.GetTime(), 0 ); + animator.PlayAnim( ANIMCHANNEL_ALL, pAnim, gameLocal.GetTime(), iBlendTime ); + iAnimLength = GetAnimator()->GetAnim( pAnim )->Length(); + } + + animDoneTime = gameLocal.GetTime() + iAnimLength; +} + +/* +================ +hhItemCabinet::SpawnIdleFX +================ +*/ +void hhItemCabinet::SpawnIdleFX() { + hhUtils::RemoveContents< idEntityPtr >( idleFxList, true ); + + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_idle"), spawnArgs.GetString("bone_idleRight"), NULL, &EV_Broadcast_AppendFxToList ); + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_idle"), spawnArgs.GetString("bone_idleLeft"), NULL, &EV_Broadcast_AppendFxToList ); +} + +//============================================================================= +// +// hhItemCabinet::SpawnDefaultItems +// +//============================================================================= + +bool hhItemCabinet::SpawnDefaultItems() { + idList items; + + if ( spawnArgs.GetString( "items", NULL ) ) { + hhUtils::SplitString( idStr(spawnArgs.GetString( "items" )), items ); + + int slot = 0; + for( int i = 0; i < items.Num(); i++ ) { + AddItem( items[i], slot ); + if ( ++slot >= CABINET_MAX_ITEMS ) { + return true; + } + } + + return true; + } + + return false; // No default items +} + +/* +================ +hhItemCabinet::SpawnItems +================ +*/ + +void hhItemCabinet::SpawnItems() { + int i; + idStr defaultName; + + int numAmmo; + + idList weaponNames; + idList weaponIndexes; + idList validWeapon; + idList ammoPercent; + idList ammoTypes; + idList itemNames; + + float total; + + bool bAutomatic = true; + + if ( !bAutomatic ) { // Items were placed by a designer, so don't automatically add any items + return; + } + + // This is the heart of the DDA system: + // list all available weapons that can have ammo in the cabinet and how much ammo the player has + + hhPlayer *player = static_cast(gameLocal.GetLocalPlayer()); + + // Build lists of all valid weapon names, ammo types and ammo names + // Note that all these must line up -- so the first weapon corresponds with the first ammo type and the first item name + hhUtils::SplitString( idStr(spawnArgs.GetString( "weaponNames" )), weaponNames ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "ammoTypes" )), ammoTypes ); + hhUtils::SplitString( idStr(spawnArgs.GetString( "itemNames" )), itemNames ); + + numAmmo = weaponNames.Num(); + + weaponIndexes.SetNum( numAmmo ); + validWeapon.SetNum( numAmmo ); + ammoPercent.SetNum( numAmmo ); + + for( i = 0; i < weaponNames.Num(); i++ ) { + weaponIndexes[i] = player->GetWeaponNum( weaponNames[i].c_str() ); + } + + // compute percentages of each ammo compared to the max allowed + for( i = 0; i < numAmmo; i++ ) { + validWeapon[i] = false; + if ( player->inventory.weapons & (1 << weaponIndexes[i] ) ) { + int ammoIndex = player->inventory.AmmoIndexForAmmoClass( ammoTypes[i].c_str() ); + ammoPercent[i] = 1.0f - player->inventory.AmmoPercentage( player, ammoIndex ); + if ( ammoPercent[i] == 0.0f ) { // Give full ammo weapons a slight chance + ammoPercent[i] = 0.01f; + } + validWeapon[i] = true; + } + } + + // for each slot in the cabinet: + for( int slot = 0; slot < numAmmo; slot++ ) { + if ( gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "emptyChance", "0.15" ) ) { // Chance the slot is empty + continue; + } + + // re-compute the total of all percentages + total = 0; + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + total += ammoPercent[i]; + } + } + + // random number from 0 - total + float random = gameLocal.random.RandomFloat() * total; + + // calculate which ammo that number is associated with and add that item to the cabinet + for( i = 0; i < numAmmo; i++ ) { + if ( validWeapon[i] ) { + if ( random <= ammoPercent[i] ) { // This is the ammo we want + AddItem( itemNames[i], slot ); // Add the item + ammoPercent[i] *= spawnArgs.GetFloat( "repeatReduce", "0.5" ); // reduce this item's chance of being chosen for the next slot + break; // No need to check further, go to the next slot + } + + random -= ammoPercent[i]; // Not the item, so remove this percent and check the next value + } + } + } +} + +/* +================ +hhItemCabinet::ResetItemList +================ +*/ +void hhItemCabinet::ResetItemList() { + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + SAFE_REMOVE( itemList[i] ); + } +} + +/* +================ +hhItemCabinet::AddItem +================ +*/ +void hhItemCabinet::AddItem( idStr itemName, int slot ) { + idDict Args; + hhItem* item = NULL; + + if ( slot < 0 || slot >= CABINET_MAX_ITEMS ) { + return; + } + + Args.SetBool( "spin", 0 ); + Args.SetFloat( "triggersize", 48.0f ); + Args.SetFloat( "respawn", 0.0f ); + Args.SetBool( "enablePickup", false ); + + item = static_cast( gameLocal.SpawnObject( itemName.c_str(), &Args ) ); + + BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_shelf"), boneList[slot] ); + + // Check if the item should be rotated + bool bRotated = false; + for ( int i = 0; i < spawnArgs.GetInt( "numRotated" ); i++ ) { + if ( !idStr::Icmp( spawnArgs.GetString( va("rotate%d", i) ), itemName.c_str() ) ) { + bRotated = true; + } + } + + if( item ) { + const char *boneName = boneList[ slot ]; + + idVec3 boneOffset; + idMat3 boneAxis; + this->GetJointWorldTransform( boneName, boneOffset, boneAxis ); + + if ( bRotated ) { + item->SetOrigin( boneOffset + GetAxis()[1] * -14 + GetAxis()[2] * 8 ); + item->SetAxis( idMat3( idVec3( 1, 0, 0 ), idVec3( 0, 0, -1), idVec3( 0, 1, 0 ) ) * GetAxis() ); + } else { + item->MoveToJoint( this, boneName ); + item->SetAxis( GetAxis() ); + } + item->BindToJoint( this, boneName, false ); + + itemList[ slot ] = item; + return; + } +} + +/* +=============== +hhItemCabinet::HandleSingleGuiCommand +=============== +*/ +bool hhItemCabinet::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + + idToken token; + + if( !src->ReadToken(&token) ) { + return false; + } + + if( token == ";" ) { + return false; + } + + if( token.Icmp("openCabinet") == 0 ) { + Event_Activate( NULL ); + return true; + } + + src->UnreadToken( &token ); + return false; +} + +/* +================ +hhItemCabinet::Event_AppendFxToIdleList +================ +*/ +void hhItemCabinet::Event_AppendFxToIdleList( hhEntityFx* fx ) { + idleFxList.Append( fx ); +} + +/* +================ +hhItemCabinet::Event_Activate +================ +*/ +void hhItemCabinet::Event_Activate( idEntity* pActivator ) { + if( gameLocal.GetTime() < animDoneTime ) { + return; + } + + if ( !SpawnDefaultItems() ) { + SpawnItems(); + } + + PlayAnim( "open", 0 ); + StartSound( "snd_open", SND_CHANNEL_ANY ); + PostEventMS( &EV_EnableItemClip, animDoneTime - gameLocal.GetTime() ); + + ActivateTargets(pActivator); + + StartSound( "snd_idle_open", SND_CHANNEL_IDLE ); +} + +/* +================ +hhItemCabinet::Event_EnableItemClip +================ +*/ +void hhItemCabinet::Event_EnableItemClip() { + for( int i = 0; i < CABINET_MAX_ITEMS; i++ ) { + if( !itemList[i].IsValid() ) { + continue; + } + + itemList[i]->EnablePickup(); + itemList[i] = NULL; + } +} diff --git a/neo/prey/Prey/game_itemcabinet.h b/neo/prey/Prey/game_itemcabinet.h new file mode 100644 index 00000000..7a25cd8a --- /dev/null +++ b/neo/prey/Prey/game_itemcabinet.h @@ -0,0 +1,51 @@ +#ifndef __HH_ITEM_CABINET_H +#define __HH_ITEM_CABINET_H + +/*********************************************************************** + +hhItemCabinet + +***********************************************************************/ +#define CABINET_MAX_ITEMS 3 + +class hhItemCabinet: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhItemCabinet ); + +public: + hhItemCabinet(); + virtual ~hhItemCabinet(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void InitBoneInfo(); + + void PlayAnim( const char* pAnimName, int iBlendTime ); + void PlayAnim( int pAnim, int iBlendTime ); + + void ResetItemList(); + void AddItem( idStr itemName, int slot ); + bool SpawnDefaultItems(); + void SpawnItems(); + void SpawnIdleFX(); + + bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + +protected: + void Event_AppendFxToIdleList( hhEntityFx* fx ); + void Event_Activate( idEntity* pActivator ); + void Event_EnableItemClip(); + virtual void Event_PostSpawn(void); + +protected: + int animDoneTime; + + idList< idEntityPtr > idleFxList; + + idEntityPtr itemList[ CABINET_MAX_ITEMS ]; + idStr boneList[ CABINET_MAX_ITEMS ]; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_jukebox.cpp b/neo/prey/Prey/game_jukebox.cpp new file mode 100644 index 00000000..1088ccf0 --- /dev/null +++ b/neo/prey/Prey/game_jukebox.cpp @@ -0,0 +1,265 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION(hhSound, hhJukeBoxSpeaker) +END_CLASS + + +const idEventDef EV_SetNumTracks("setNumTracks", "d"); +const idEventDef EV_SetTrack("setTrack", "d"); +const idEventDef EV_PlaySelected("playSelected", NULL); +const idEventDef EV_TrackOver("", NULL); +const idEventDef EV_SetJukeboxVolume("setvolume", "f"); + +CLASS_DECLARATION(hhConsole, hhJukeBox) + EVENT( EV_SetNumTracks, hhJukeBox::Event_SetNumTracks) + EVENT( EV_SetTrack, hhJukeBox::Event_SetTrack) + EVENT( EV_PlaySelected, hhJukeBox::Event_PlaySelected) + EVENT( EV_TrackOver, hhJukeBox::Event_TrackOver) + EVENT( EV_SetJukeboxVolume, hhJukeBox::Event_SetVolume) +END_CLASS + + +void hhJukeBox::Spawn() { + track = 1; + currentHistorySample = 0; + numTracks = spawnArgs.GetInt("numtracks", "1"); + volume = spawnArgs.GetFloat("volume"); + UpdateView(); +} + +void hhJukeBox::Save(idSaveGame *savefile) const { + int i; + + savefile->WriteFloat( volume ); + savefile->WriteInt( track ); + savefile->WriteInt( numTracks ); + savefile->WriteInt( currentHistorySample ); + + savefile->WriteInt( speakers.Num() ); // Saving of idList + for( i = 0; i < speakers.Num(); i++ ) { + savefile->WriteObject(speakers[i]); + } +} + +void hhJukeBox::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadFloat( volume ); + savefile->ReadInt( track ); + savefile->ReadInt( numTracks ); + savefile->ReadInt( currentHistorySample ); + + speakers.Clear(); + savefile->ReadInt( num ); + speakers.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast(speakers[i]) ); + } + + UpdateVolume(); +} + +void hhJukeBox::ConsoleActivated() { + BecomeActive(TH_MISC3); +} + +void hhJukeBox::UpdateView() { + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + gui->SetStateInt("track", track); + gui->SetStateFloat("volume", volume); + } +} + +void hhJukeBox::ClearSpectrum() { + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + gui->SetStateFloat("amplitude", 0.0f); + + for (int ix=0; ix<10; ix++) { + gui->SetStateFloat(va("amplitude%d", ix), 0.0f); + } + gui->StateChanged(gameLocal.time); + } +} + +void hhJukeBox::SetTrack(int newTrack) { + track = idMath::ClampInt(1, numTracks, newTrack); +} + +void hhJukeBox::PlayCurrentTrack() { + const char *shaderName = spawnArgs.GetString(va("snd_song%d", track), NULL); + const idSoundShader *shader = declManager->FindSound(shaderName); + int time = 0; + + // Clear up any previous state, even if using another mixer at the time + StopCurrentTrack(); + + // In OpenAL, samples that are out of range pause instead of mute so the targetted speakers can get out of sync. + if (shader && targets.Num() && !cvarSystem->GetCVarBool("s_useOpenAL")) { + for (int ix=0; ixStartSoundShader(shader, SND_CHANNEL_VOICE, 0, true, &time); + } + } + } + else { + StartSound(va("snd_song%d", track), SND_CHANNEL_VOICE, 0, true, &time); + } + UpdateVolume(); + CancelEvents(&EV_TrackOver); + PostEventMS(&EV_TrackOver, time + 500); +} + +void hhJukeBox::StopCurrentTrack() { + CancelEvents(&EV_TrackOver); + ClearSpectrum(); + + // Stop speakers and jukebox so no mixer switches can screw us up + StopSound(SND_CHANNEL_VOICE, true); + for (int ix=0; ixStopSound(SND_CHANNEL_VOICE, true); + } + } +} + +bool hhJukeBox::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("prevtrack") == 0) { + SetTrack(track-1); + UpdateView(); + } + else if (token.Icmp("nexttrack") == 0) { + SetTrack(track+1); + UpdateView(); + } + else if (token.Icmp("selecttrack") == 0) { + BecomeActive(TH_MISC3); + PlayCurrentTrack(); + UpdateView(); + } + else if (token.Icmp("turnoff") == 0) { + StopCurrentTrack(); + BecomeInactive(TH_MISC3); + UpdateView(); + } + else if (token.Icmp("turnon") == 0) { + BecomeActive(TH_MISC3); + UpdateView(); + } + else if (token.Icmp("volumeup") == 0) { + volume = idMath::ClampFloat(0.0f, 1.0f, volume + 0.02f); + UpdateVolume(); + UpdateView(); + } + else if (token.Icmp("volumedown") == 0) { + volume = idMath::ClampFloat(0.0f, 1.0f, volume - 0.02f); + UpdateVolume(); + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhJukeBox::UpdateEntityVolume(idEntity *ent) { + ent->HH_SetSoundVolume(volume, SND_CHANNEL_VOICE); +} + +void hhJukeBox::UpdateVolume() { + if (targets.Num() && !cvarSystem->GetCVarBool("s_useOpenAL")) { + for (int ix=0; ixCurrentlyPlaying()) { + amplitude = refSound.referenceSound->CurrentAmplitude(); + } + else if (targets.Num() && targets[0].IsValid() && targets[0].GetEntity()->IsType(hhSound::Type) ) { + amplitude = static_cast(targets[0].GetEntity())->GetCurrentAmplitude(SND_CHANNEL_VOICE); + } + + amplitude = idMath::ClampFloat(0.0f, 1.0f, amplitude); + idUserInterface *gui = renderEntity.gui[0]; // Interface area + if (gui) { + gui->SetStateFloat("amplitude", amplitude); + + currentHistorySample = (currentHistorySample+1)%10; + gui->SetStateFloat(va("amplitude%d", currentHistorySample), amplitude); + gui->StateChanged(gameLocal.time); + } + gui = renderEntity.gui[1]; // Outer jukebox + if (gui) { + gui->SetStateFloat("amplitude", amplitude); + } + } +} + +void hhJukeBox::Event_SetNumTracks(int newNumTracks) { + numTracks = newNumTracks; +} + +void hhJukeBox::Event_SetTrack(int newTrack) { + SetTrack(newTrack); + UpdateView(); +} + +void hhJukeBox::Event_PlaySelected() { + PlayCurrentTrack(); + BecomeActive(TH_MISC3); + UpdateView(); +} + +void hhJukeBox::Event_TrackOver() { + ClearSpectrum(); + //loop to beginning + if ( track+1 > numTracks ) { + SetTrack(1); + } else { + SetTrack(track+1); + } + PlayCurrentTrack(); + UpdateView(); +} + +void hhJukeBox::Event_SetVolume(float vol) { + volume = idMath::ClampFloat(0.0f, 1.0f, vol); + UpdateVolume(); + UpdateView(); +} + + diff --git a/neo/prey/Prey/game_jukebox.h b/neo/prey/Prey/game_jukebox.h new file mode 100644 index 00000000..bc03d56f --- /dev/null +++ b/neo/prey/Prey/game_jukebox.h @@ -0,0 +1,47 @@ + +#ifndef __GAME_JUKEBOX_H__ +#define __GAME_JUKEBOX_H__ + + +class hhJukeBox : public hhConsole { +public: + CLASS_PROTOTYPE( hhJukeBox ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void ConsoleActivated(); + virtual void Think(); + + void ClearSpectrum(); + void SetTrack(int track); + void PlayCurrentTrack(); + void StopCurrentTrack(); + void UpdateView(); + void UpdateVolume(); + void UpdateEntityVolume(idEntity *ent); + + void Event_SetNumTracks(int newNumTracks); + void Event_SetTrack(int newTrack); + void Event_PlaySelected(); + void Event_TrackOver(); + void Event_SetVolume(float vol); + +protected: + float volume; + int track; + int numTracks; + int currentHistorySample; + idList speakers; +}; + + +class hhJukeBoxSpeaker : public hhSound { +public: + CLASS_PROTOTYPE( hhJukeBoxSpeaker ); +}; + + +#endif diff --git a/neo/prey/Prey/game_jumpzone.cpp b/neo/prey/Prey/game_jumpzone.cpp new file mode 100644 index 00000000..ecaf59db --- /dev/null +++ b/neo/prey/Prey/game_jumpzone.cpp @@ -0,0 +1,129 @@ +// Game_JumpZone.cpp +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +const idEventDef EV_ResetSlopeCheck("", "d"); + +CLASS_DECLARATION(hhTrigger, hhJumpZone) + EVENT( EV_Touch, hhJumpZone::Event_Touch ) + EVENT( EV_Enable, hhJumpZone::Event_Enable ) + EVENT( EV_Disable, hhJumpZone::Event_Disable ) + EVENT( EV_ResetSlopeCheck, hhJumpZone::Event_ResetSlopeCheck ) +END_CLASS + +void hhJumpZone::Spawn(void) { + + velocity = spawnArgs.GetVector("velocity"); + pitchDegrees = spawnArgs.GetFloat("jumpPitch"); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +} + +void hhJumpZone::Save(idSaveGame *savefile) const { + savefile->WriteVec3( velocity ); + savefile->WriteFloat( pitchDegrees ); +} + +void hhJumpZone::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( velocity ); + savefile->ReadFloat( pitchDegrees ); +} + + +// Given a pitch angle, calculate a speed to get us to destination +static float JumpBallistics( const idVec3 &start, const idVec3 &end, float pitch, float gravity ) { +/* + speed = sqrt( + -0.5f * gravity * ( x / cos(pitch) )^2 + ----------------------------------------- + y - x * tan(pitch) + ); +*/ + float pitchRadians = DEG2RAD(pitch); + float speed = 0.0f; + idVec3 toTarget = end - start; + float dist = toTarget.Length(); + float a = dist / idMath::Cos(pitchRadians); + float num = -0.5f * gravity * a*a; + float den = toTarget.z - dist * idMath::Tan(pitchRadians); + if (den != 0.0f) { + speed = idMath::Sqrt( num / den ); + } + return speed; +} + +idVec3 hhJumpZone::CalculateJumpVelocity() { + idVec3 destination = GetOrigin() + idVec3(0,0,200); + if (targets.Num() == 0 || !targets[0].IsValid()) { + // Use explicit velocity + return velocity; + } + + destination = targets[0]->GetOrigin(); + + idVec3 toTarget = destination - GetOrigin(); + + // Given the angle, calculate a speed to get us to destination + float speed = JumpBallistics(GetOrigin(), destination, pitchDegrees, DEFAULT_GRAVITY); + + idAngles ang; + ang.Set(-pitchDegrees, toTarget.ToYaw(), 0.0f); + return ang.ToForward() * speed; +} + +/* +================ +hhJumpZone::Event_Enable +================ +*/ +void hhJumpZone::Event_Enable( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +} + +/* +================ +hhJumpZone::Event_Disable +================ +*/ +void hhJumpZone::Event_Disable( void ) { + GetPhysics()->SetContents( 0 ); +} + + +/* +================ +hhJumpZone::Event_Touch +================ +*/ +void hhJumpZone::Event_Touch( idEntity *other, trace_t *trace ) { + if (other) { + // Enable slope checks on player in case it was turned off by gravity zone. Need it on to + // recognize getting thrown off the ground + if (other->IsType( hhPlayer::Type ) && other->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(other->GetPhysics())->SetSlopeCheck(true); + // Post an event to turn it back off? + other->PostEventMS(&EV_ResetSlopeCheck, 200, other->entityNumber); + } + other->GetPhysics()->SetLinearVelocity( CalculateJumpVelocity() ); + } +} + +void hhJumpZone::Event_ResetSlopeCheck(int entNum) { + idEntity *entityList[100]; + + // If the player is still encroaching on an inward gravity zone, reset slope check (off) + idEntity *player = gameLocal.entities[entNum]; + if (player && player->IsType(hhPlayer::Type) && player->GetPhysics()->IsType(hhPhysics_Player::Type)) { + int num = gameLocal.clip.EntitiesTouchingBounds(player->GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, entityList, 100); + for (int ix=0; ixIsType(hhGravityZoneInward::Type)) { + static_cast(player->GetPhysics())->SetSlopeCheck(false); + break; + } + } + } +} \ No newline at end of file diff --git a/neo/prey/Prey/game_jumpzone.h b/neo/prey/Prey/game_jumpzone.h new file mode 100644 index 00000000..5dd117da --- /dev/null +++ b/neo/prey/Prey/game_jumpzone.h @@ -0,0 +1,28 @@ +// Game_JumpZone.h +// + +#ifndef __GAME_JUMPZONE_H__ +#define __GAME_JUMPZONE_H__ + +class hhJumpZone : public hhTrigger { +public: + CLASS_PROTOTYPE( hhJumpZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idVec3 CalculateJumpVelocity(); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_ResetSlopeCheck(int entNum); + +public: + idVec3 velocity; + float pitchDegrees; +}; + + +#endif /* __GAME_JUMPZONE_H__ */ diff --git a/neo/prey/Prey/game_light.cpp b/neo/prey/Prey/game_light.cpp new file mode 100644 index 00000000..a5c534b7 --- /dev/null +++ b/neo/prey/Prey/game_light.cpp @@ -0,0 +1,103 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhLight + +***********************************************************************/ + +//HUMANHEAD: aob +const idEventDef EV_Light_StartAltMode( "startAltMode" ); +//HUMANHEAD END + +CLASS_DECLARATION( idLight, hhLight ) + EVENT( EV_PostSpawn, hhLight::Event_SetTargetHandles ) + EVENT( EV_ResetTargetHandles, hhLight::Event_SetTargetHandles ) + EVENT( EV_Light_StartAltMode, hhLight::Event_StartAltMode ) +END_CLASS + +/* +================ +hhLight::SetLightCenter + +HUMANHEAD cjr +================ +*/ +void hhLight::SetLightCenter( idVec3 center ) { + renderLight.lightCenter = center; + PresentLightDefChange(); +} + +/* +================ +hhLight::StartAltSound +================ +*/ +void hhLight::StartAltSound() { + if ( refSound.shader ) { + StopSound( SND_CHANNEL_ANY ); + const idSoundShader *alternate = refSound.shader->GetAltSound(); + if ( alternate ) { + StartSoundShader( alternate, SND_CHANNEL_ANY ); + } + } +} + +/* +================ +hhLight::Event_SetTargetHandles + + set the same sound def handle on all targeted entities + +HUMANHEAD: aob +================ +*/ +void hhLight::Event_SetTargetHandles( void ) { + int i; + idEntity *targetEnt = NULL; + + if ( !refSound.referenceSound ) { + return; + } + + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt ) { + if( targetEnt->IsType(idLight::Type) ) { + static_cast(targetEnt)->SetLightParent( this ); + } + + targetEnt->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + targetEnt->GetRenderEntity()->referenceSound = renderEntity.referenceSound; + + // update the renderEntity to the renderer + targetEnt->UpdateVisuals(); + } + } +} + +/* +================ +hhLight::Event_StartAltMode +================ +*/ +void hhLight::Event_StartAltMode() { + //Copied fron idLight::BecomeBroken + + // 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; + + StartAltSound(); + + UpdateVisuals(); +} \ No newline at end of file diff --git a/neo/prey/Prey/game_light.h b/neo/prey/Prey/game_light.h new file mode 100644 index 00000000..281803fc --- /dev/null +++ b/neo/prey/Prey/game_light.h @@ -0,0 +1,25 @@ +#ifndef __HH_GAME_LIGHT_H +#define __HH_GAME_LIGHT_H + +/* +=============================================================================== + +hhLight + +=============================================================================== +*/ + +class hhLight : public idLight { + CLASS_PROTOTYPE( hhLight ); + +public: + void SetLightCenter( idVec3 center ); + + void StartAltSound(); + +protected: + void Event_SetTargetHandles( void ); + void Event_StartAltMode(); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_lightfixture.cpp b/neo/prey/Prey/game_lightfixture.cpp new file mode 100644 index 00000000..a9aa56ed --- /dev/null +++ b/neo/prey/Prey/game_lightfixture.cpp @@ -0,0 +1,210 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhAFEntity, hhLightFixture ) + EVENT( EV_PostSpawn, hhLightFixture::Event_PostSpawn ) + EVENT( EV_Hide, hhLightFixture::Event_Hide ) + EVENT( EV_Show, hhLightFixture::Event_Show ) +END_CLASS + +/* +=============== +hhLightFixture::Spawn +=============== +*/ +void hhLightFixture::Spawn() { + collisionBone = INVALID_JOINT; + boundLight = NULL; + + PostEventMS( &EV_PostSpawn, 10 ); +} + +void hhLightFixture::Save(idSaveGame *savefile) const { + savefile->WriteInt( collisionBone ); + boundLight.Save(savefile); +} + +void hhLightFixture::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)collisionBone ); + boundLight.Restore(savefile); +} + +/* +=============== +hhLightFixture::~hhLightFixture +=============== +*/ +hhLightFixture::~hhLightFixture() { + RemoveLight(); +} + +/* +=============== +hhLightFixture::Damage +=============== +*/ +void hhLightFixture::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( collisionBone != INVALID_JOINT && (location == INVALID_JOINT || collisionBone == location) ) { + hhAFEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + +/* +=============== +hhLightFixture::Killed +=============== +*/ +void hhLightFixture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhAFEntity::Killed( inflictor, attacker, damage, dir, location ); + + if( StillBound(boundLight.GetEntity()) ) { + //boundLight->SetShader( spawnArgs.GetString("mtr_lightDestroyed") ); + boundLight->BecomeBroken( attacker ); + } + + const char* skinName = spawnArgs.GetString( "skin_destroyed" ); + if( skinName && skinName[0] ) { + SetSkinByName( skinName ); + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + // set the state parm + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + + UpdateVisuals(); +} + +/* +=============== +hhLightFixture::GetBoundLight +=============== +*/ +void hhLightFixture::GetBoundLight() { + idVec3 color; + + boundLight = SearchForBoundLight(); + if( boundLight.IsValid() ) { + boundLight->fl.takedamage = false; + + collisionBone = GetAnimator()->GetJointHandle( boundLight->spawnArgs.GetString("bindToJoint") ); + + SetColor( boundLight->spawnArgs.GetVector("_color", "1 1 1") ); + } +} + + +/* +=============== +hhLightFixture::SearchForBoundLight +=============== +*/ +idLight* hhLightFixture::SearchForBoundLight() { + for( idEntity* entity = GetTeamChain(); entity; entity = entity->GetTeamChain() ) { + if( entity && entity->IsType(idLight::Type) ) { + return static_cast( entity ); + } + } + + return NULL; +} + +/* +=============== +hhLightFixture::StillBound +=============== +*/ +bool hhLightFixture::StillBound( const idLight* light ) { + return (light) ? light->IsBoundTo(this) : false; +} + +/* +=============== +hhLightFixture::RemoveLight +=============== +*/ +void hhLightFixture::RemoveLight() { + SAFE_REMOVE( boundLight ); +} + +/* +================ +hhLightFixture::Present +================ +*/ +void hhLightFixture::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // add the model + hhAFEntity::Present(); + + // reference the sound for shader synced effects + if ( boundLight.IsValid() && StillBound(boundLight.GetEntity()) ) { + renderEntity.referenceSound = boundLight->GetSoundEmitter(); + } + else { + renderEntity.referenceSound = refSound.referenceSound; + } + + PresentModelDefChange(); +} + +/* +================ +hhLightFixture::PresentModelDefChange +================ +*/ +void hhLightFixture::PresentModelDefChange( void ) { + + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +=============== +hhLightFixture::Event_PostSpawn +=============== +*/ +void hhLightFixture::Event_PostSpawn() { + GetBoundLight(); +} + +/* +=============== +hhLightFixture::Event_Hide +=============== +*/ +void hhLightFixture::Event_Hide() { + idEntity::Event_Hide(); + + if( boundLight.IsValid() ) { + boundLight->Off(); + } +} + +/* +=============== +hhLightFixture::Event_Show +=============== +*/ +void hhLightFixture::Event_Show() { + idEntity::Event_Show(); + + if( boundLight.IsValid() ) { + boundLight->On(); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/game_lightfixture.h b/neo/prey/Prey/game_lightfixture.h new file mode 100644 index 00000000..725bba81 --- /dev/null +++ b/neo/prey/Prey/game_lightfixture.h @@ -0,0 +1,37 @@ +#ifndef __HH_LIGHT_FIXTURE_H +#define __HH_LIGHT_FIXTURE_H + +class hhLightFixture : public hhAFEntity { + CLASS_PROTOTYPE( hhLightFixture ); + +public: + ~hhLightFixture(); + + void Spawn(); + 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 Present( void ); + +protected: + void GetBoundLight(); + idLight* SearchForBoundLight(); + bool StillBound( const idLight* light ); + void RemoveLight(); + + void PresentModelDefChange( void ); + +protected: + void Event_PostSpawn(); + void Event_Hide(); + void Event_Show(); + +protected: + jointHandle_t collisionBone; + + idEntityPtr boundLight; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_mine.cpp b/neo/prey/Prey/game_mine.cpp new file mode 100644 index 00000000..cea9efb8 --- /dev/null +++ b/neo/prey/Prey/game_mine.cpp @@ -0,0 +1,432 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhMine +// +//========================================================================== + +const idEventDef EV_ExplodeDamage( "explodeDamage", "e" ); +const idEventDef EV_MineHover( "" ); + +CLASS_DECLARATION(hhMoveable, hhMine) + EVENT( EV_Activate, hhMine::Event_Trigger) + EVENT( EV_ExplodeDamage, hhMine::Event_ExplodeDamage) + EVENT( EV_Remove, hhMine::Event_Remove) + EVENT( EV_ExplodedBy, hhMine::Event_ExplodedBy ) + EVENT( EV_MineHover, hhMine::Event_MineHover ) +END_CLASS + +void hhMine::Spawn(void) { + spawner = NULL; + fl.takedamage = spawnArgs.GetBool("takedamage", "1"); + + idVec3 worldGravityDir( gameLocal.GetGravity() ); + float gravityMagnitude = spawnArgs.GetFloat( "gravity", va("%.2f", worldGravityDir.Normalize()) ); + GetPhysics()->SetGravity( gravityMagnitude * worldGravityDir ); + + bScaleIn = spawnArgs.GetBool("scalein"); + if (bScaleIn) { + fadeAlpha.Init(gameLocal.time, 2000, 0.01f, 1.0f); + BecomeActive(TH_MISC1); + } + + if (spawnArgs.FindKey("snd_spawn")) { + StartSound( "snd_spawn", SND_CHANNEL_ANY ); + } + + if (spawnArgs.FindKey("snd_idle")) { + StartSound( "snd_idle", SND_CHANNEL_IDLE ); + } + + bDetonateOnCollision = spawnArgs.GetBool("DetonateOnCollision"); + bDamageOnCollision = spawnArgs.GetBool("DamageOnCollision"); + bExploded = false; + + if (!spawnArgs.GetBool("nodrop") && GetPhysics()->GetGravity() == vec3_origin) { + gameLocal.Warning("zero grav object without nodrop will not move: %s", name.c_str()); + } + + if (bDetonateOnCollision) { + SpawnTrigger(); + } +} + +void hhMine::Save(idSaveGame *savefile) const { + savefile->WriteObject( spawner ); + savefile->WriteBool( bDetonateOnCollision ); + savefile->WriteBool( bDamageOnCollision ); + + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); + + savefile->WriteBool( bScaleIn ); + savefile->WriteBool( bExploded ); +} + +void hhMine::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadObject( reinterpret_cast(spawner) ); + savefile->ReadBool( bDetonateOnCollision ); + savefile->ReadBool( bDamageOnCollision ); + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); + + savefile->ReadBool( bScaleIn ); + savefile->ReadBool( bExploded ); +} + +void hhMine::SpawnTrigger() { + idEntity *trigger; + idDict Args; + + Args.Set( "target", name.c_str() ); + Args.Set( "mins", spawnArgs.GetString("triggerMins") ); + Args.Set( "maxs", spawnArgs.GetString("triggerMaxs") ); + Args.Set( "bind", name.c_str() ); + Args.SetVector( "origin", GetOrigin() ); + Args.SetMatrix( "rotation", GetAxis() ); + trigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &Args ); +} + +void hhMine::Think() { + if (thinkFlags & TH_MISC1) { + if (bScaleIn) { + if (fadeAlpha.IsDone(gameLocal.time)) { + SetDeformation(DEFORMTYPE_SCALE, 0.0f); // Turn scaling off + bScaleIn = false; + BecomeInactive(TH_MISC1); + } + else { + SetDeformation(DEFORMTYPE_SCALE, fadeAlpha.GetCurrentValue(gameLocal.time)); + } + } + } + //TODO: Could make these come to rest when gravity is zero and not moving + RunPhysics(); + Present(); +} + +void hhMine::Launch(idVec3 &velocity, idVec3 &avelocity) { + GetPhysics()->SetLinearVelocity(velocity); + GetPhysics()->SetAngularVelocity(avelocity); + BecomeActive(TH_MISC1); +} + +void hhMine::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // skip idMoveable::Damage which handles damage differently + idEntity::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + +void hhMine::ApplyImpulse(idEntity * ent, int id, const idVec3 &point, const idVec3 &impulse) { + if (impulse == vec3_origin && ent->IsType(idActor::Type)) { // Just pushed + // Apply player's velocity to the pod so it moves at same rate as player and keeps going + idVec3 newimpulse = ent->GetPhysics()->GetLinearVelocity() * GetPhysics()->GetMass(); + hhMoveable::ApplyImpulse(ent, id, point, newimpulse); +// GetPhysics()->SetLinearVelocity(ent->GetPhysics()->GetLinearVelocity()*4); + } + if (bDetonateOnCollision) { + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS(&EV_ExplodedBy, 0, this); + } + hhMoveable::ApplyImpulse(ent, id, point, impulse); +} + +bool hhMine::AllowCollision( const trace_t &collision ) { + idEntity *ent = gameLocal.entities[collision.c.entityNum]; + if ( ent && ent->IsType(hhShuttleForceField::Type) ) { + return false; // Allow asteroids to go through shuttle forcefields + } + return true; +} + +bool hhMine::Collide( const trace_t &collision, const idVec3 &velocity ) { + const char *decal; + idEntity *ent = gameLocal.entities[collision.c.entityNum]; + + // project decal + decal = spawnArgs.RandomPrefix( "mtr_decal", gameLocal.random ); + if ( decal && *decal ) { + gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, spawnArgs.GetFloat( "decal_trace", "128.0" ), true, spawnArgs.GetFloat( "decal_size", "6.0" ), decal ); + } + + if (bDamageOnCollision) { + const idKeyValue *kv = spawnArgs.FindKey("def_damage"); + if (ent && kv != NULL) { + ent->Damage(this, gameLocal.world, velocity, kv->GetValue().c_str(), 1.0f, 0); + } + } + if (bDetonateOnCollision) { + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS(&EV_ExplodedBy, 0, this); + + return true; + } + return false; +} + +void hhMine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; // nla - Prevent killed from being called too many times. + + // Need to post an event because already in physics code now, can't nest a projectile spawn from within physics code + // currently, because rigid body physics ::Evaluate is not reentrant friendly (has a static timer) + PostEventMS(&EV_ExplodedBy, 0, attacker); +} + +void hhMine::Explode( idEntity *attacker ) { + hhFxInfo fxInfo; + int splash_damage_delay = SEC2MS( spawnArgs.GetFloat( "splash_damage_delay", "0.5" ) ); + + if (bExploded || IsHidden() ) { + return; + } + + bExploded = true; + + // Activate targets + ActivateTargets( attacker ); + + // Set for removal + if ( spawnArgs.GetBool( "respawn" ) ) { + fl.takedamage = true; + PostEventSec( &EV_Show, spawnArgs.GetFloat( "respawn_delay", "2" ) ); + bExploded = false; + Hide(); + } else { + PostEventMS( &EV_Remove, 1500 + splash_damage_delay ); + GetPhysics()->SetContents( 0 ); + fl.takedamage = false; + Hide(); + } + + RemoveBinds(); + + // Spawn explosion + StopSound( SND_CHANNEL_IDLE ); + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + //fixme: if we stay with moveables, this can be replaced by key "gib" + if ( spawnArgs.GetString("def_debrisspawner")[0] ) { + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), GetOrigin()); + } + + //fixme: fx are spawned by debris system? + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", GetOrigin(), GetAxis(), &fxInfo ); + + if (spawnArgs.FindKey("def_splash_damage") != NULL) { + PostEventMS(&EV_ExplodeDamage, splash_damage_delay, attacker); + } +} + +bool hhMine::WasSpawnedBy(idEntity *theSpawner) { + return (spawner == theSpawner); +} + +void hhMine::Event_Trigger( idEntity *activator ) { + Explode(activator); +} + +void hhMine::Event_ExplodedBy( idEntity *activator) { + Explode(activator); +} + +void hhMine::Event_MineHover() { + GetPhysics()->SetLinearVelocity( vec3_zero ); +} + +void hhMine::Event_Remove() { + if (spawner) { // notify spawner + spawner->MineRemoved(this); + spawner = NULL; + } + hhMoveable::Event_Remove(); +} + +void hhMine::Event_ExplodeDamage( idEntity *attacker ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, spawnArgs.GetString("def_splash_damage") ); +} + + +//========================================================================== +// +// hhMineSpawner +// +//========================================================================== +const idEventDef EV_SpawnMine("SpawnMine", NULL); + +CLASS_DECLARATION(hhAnimatedEntity, hhMineSpawner) + EVENT( EV_Activate, hhMineSpawner::Event_Activate) + EVENT( EV_SpawnMine, hhMineSpawner::Event_SpawnMine) + EVENT( EV_Remove, hhMineSpawner::Event_Remove ) +END_CLASS + +void hhMineSpawner::Spawn(void) { + population = 0; + targetPopulation = spawnArgs.GetInt("population"); + mineVelocity = spawnArgs.GetVector("velocity"); + mineAVelocity = spawnArgs.GetVector("avelocity"); + bRandomDirection = spawnArgs.GetBool("randdir"); + bRandomRotation = spawnArgs.GetBool("randrot"); + spawnDelay = SEC2MS(spawnArgs.GetFloat("spawndelay")); + speed = mineVelocity.Length(); + aspeed = mineAVelocity.Length(); + active = spawnArgs.GetBool("start_on"); + + GetPhysics()->SetContents(0); + if (targetPopulation > 0 && active) { + PostEventMS(&EV_SpawnMine, 2000); + } + BecomeActive(TH_THINK); // Need to be active in order to get dormant messages +} + +void hhMineSpawner::Save(idSaveGame *savefile) const { + savefile->WriteInt( population ); + savefile->WriteInt( targetPopulation ); + savefile->WriteVec3( mineVelocity ); + savefile->WriteVec3( mineAVelocity ); + savefile->WriteBool( bRandomDirection ); + savefile->WriteBool( bRandomRotation ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( aspeed ); + savefile->WriteInt( spawnDelay ); + savefile->WriteBool( active ); +} + +void hhMineSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( population ); + savefile->ReadInt( targetPopulation ); + savefile->ReadVec3( mineVelocity ); + savefile->ReadVec3( mineAVelocity ); + savefile->ReadBool( bRandomDirection ); + savefile->ReadBool( bRandomRotation ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( aspeed ); + savefile->ReadInt( spawnDelay ); + savefile->ReadBool( active ); +} + +void hhMineSpawner::DormantBegin() { + // Remove all pending spawn events + CancelEvents(&EV_SpawnMine); +} + +void hhMineSpawner::DormantEnd() { + // restart if active + CheckPopulation(); +} + +void hhMineSpawner::CheckPopulation() { + if (active && population < targetPopulation) { + CancelEvents(&EV_SpawnMine); + PostEventMS(&EV_SpawnMine, spawnDelay); + } +} + +void hhMineSpawner::MineRemoved(hhMine *mine) { + --population; + CheckPopulation(); +} + +void hhMineSpawner::SpawnMine() { + if (fl.isDormant) { + return; + } + + const char *mineDefName = spawnArgs.GetString("def_mine"); + idBounds bounds = GetPhysics()->GetAbsBounds(); + idVec3 location = hhUtils::RandomPointInBounds(bounds.Expand(-33)); + + // If won't fit, wait and spawn later + if ( spawnArgs.GetBool( "force_spawn", "0" ) || hhUtils::EntityDefWillFit(mineDefName, location, mat3_identity, CONTENTS_SOLID, NULL)) { + if (bRandomDirection) { + mineVelocity = hhUtils::RandomVector() * speed; + } + if (bRandomRotation) { + mineAVelocity = hhUtils::RandomVector() * aspeed; + } + idDict args; + args.SetVector("origin", location); + + hhMine *mine = static_cast(gameLocal.SpawnObject(mineDefName, &args)); + if (mine) { + ActivateTargets( this ); + mine->SetSpawner(this); + mine->Launch(mineVelocity, mineAVelocity); + float stopDelay = spawnArgs.GetFloat( "stop_delay", "0" ); + if ( stopDelay > 0.0f ) { + mine->PostEventSec( &EV_MineHover, stopDelay ); + } + } + ++population; + CheckPopulation(); + } + else { + // Check population with low delay, since it's already time to spawn + if (active && population < targetPopulation) { + CancelEvents(&EV_SpawnMine); + PostEventMS(&EV_SpawnMine, 500); + } + } +} + +void hhMineSpawner::Event_Activate(idEntity *activator) { + if ( spawnArgs.GetBool( "limit_triggers", "0" ) ) { + active = false; + CancelEvents(&EV_SpawnMine); // Turn off + if ( population < targetPopulation ) { + SpawnMine(); // This also will continue to check population + } + return; + } + + // Use population=0 to spawn purely based on triggers + if (active && targetPopulation != 0) { + active = false; + CancelEvents(&EV_SpawnMine); // Turn off + } + else { + active = true; + SpawnMine(); // This also will continue to check population + } +} + +void hhMineSpawner::Event_SpawnMine() { + SpawnMine(); +} + +void hhMineSpawner::Event_Remove() { + // Handle removal gracefully + idEntity *ent = NULL; + hhMine *mine = NULL; + for (int ix=0; ixIsType(hhMine::Type)) { + mine = static_cast(ent); + if (mine->WasSpawnedBy(this)) { + mine->SetSpawner(NULL); + if ( spawnArgs.GetBool( "explode_on_remove" ) ) { + mine->Explode( NULL ); + } + } + } + } + idEntity::Event_Remove(); +} diff --git a/neo/prey/Prey/game_mine.h b/neo/prey/Prey/game_mine.h new file mode 100644 index 00000000..80ee8c04 --- /dev/null +++ b/neo/prey/Prey/game_mine.h @@ -0,0 +1,82 @@ + +#ifndef __GAME_MINE_H__ +#define __GAME_MINE_H__ + +extern const idEventDef EV_ExplodeDamage; + +class hhMineSpawner; + +class hhMine : public hhMoveable { +public: + CLASS_PROTOTYPE( hhMine ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Launch(idVec3 &velocity, idVec3 &avelocity); + virtual bool AllowCollision( const trace_t &collision ); + virtual void ApplyImpulse(idEntity * ent, int id, const idVec3 &point, const idVec3 &impulse); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Explode( idEntity *attacker ); + void SetSpawner(hhMineSpawner *s) { spawner = s; } + bool WasSpawnedBy(idEntity *theSpawner); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Think(); + +protected: + void SpawnTrigger(); + void Event_ExplodeDamage( idEntity *attacker ); + void Event_Trigger( idEntity *activator ); + virtual void Event_Remove(); + void Event_ExplodedBy( idEntity *activator); + void Event_MineHover(); + +protected: + hhMineSpawner * spawner; // Entity that spawned this object + bool bDetonateOnCollision; + bool bDamageOnCollision; + idInterpolate fadeAlpha; + bool bScaleIn; + bool bExploded; +}; + + +extern const idEventDef EV_SpawnMine; + +class hhMineSpawner : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhMineSpawner ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void MineRemoved(hhMine *mine); + void DormantBegin(); + void DormantEnd(); + +protected: + virtual void SpawnMine(); + void CheckPopulation(); + + void Event_Activate(idEntity *activator); + void Event_SpawnMine(); + virtual void Event_Remove(); + +protected: + int population; + int targetPopulation; + idVec3 mineVelocity; + idVec3 mineAVelocity; + bool bRandomDirection; + bool bRandomRotation; + float speed; // Precomputed speed based on mineVelocity + float aspeed; // Precomputed speed based on mineAVelocity + int spawnDelay; + bool active; +}; + + +#endif diff --git a/neo/prey/Prey/game_misc.cpp b/neo/prey/Prey/game_misc.cpp new file mode 100644 index 00000000..2651fbc7 --- /dev/null +++ b/neo/prey/Prey/game_misc.cpp @@ -0,0 +1,404 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhWallWalkable +// +//========================================================================== + +CLASS_DECLARATION( idStaticEntity, hhWallWalkable ) + EVENT(EV_Activate, hhWallWalkable::Event_Activate) +END_CLASS + +void hhWallWalkable::Spawn( void ) { + wallwalkOn = spawnArgs.GetBool("active"); + flicker = spawnArgs.GetBool("flicker"); + + // Get skin references (already precached) + onSkin = declManager->FindSkin( spawnArgs.GetString("skinOn") ); + offSkin = declManager->FindSkin( spawnArgs.GetString("skinOff") ); + + if (wallwalkOn) { + SetSkin( onSkin ); + alphaOn.Init(gameLocal.time, 0, 1.0f, 1.0f); + } + else { + SetSkin( offSkin ); + alphaOn.Init(gameLocal.time, 0, 0.0f, 0.0f); + } + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + UpdateVisuals(); + + fl.networkSync = true; +} + +void hhWallWalkable::Save(idSaveGame *savefile) const { + savefile->WriteFloat( alphaOn.GetStartTime() ); // hhHermiteInterpolate + savefile->WriteFloat( alphaOn.GetDuration() ); + savefile->WriteFloat( alphaOn.GetStartValue() ); + savefile->WriteFloat( alphaOn.GetEndValue() ); + savefile->WriteFloat( alphaOn.GetS1() ); + savefile->WriteFloat( alphaOn.GetS2() ); + + savefile->WriteBool( wallwalkOn ); + savefile->WriteBool( flicker ); + savefile->WriteSkin( onSkin ); + savefile->WriteSkin( offSkin ); +} + +void hhWallWalkable::Restore( idRestoreGame *savefile ) { + float set, set2; + + savefile->ReadFloat( set ); // hhHermiteInterpolate + alphaOn.SetStartTime( set ); + savefile->ReadFloat( set ); + alphaOn.SetDuration( set ); + savefile->ReadFloat( set ); + alphaOn.SetStartValue(set); + savefile->ReadFloat( set ); + alphaOn.SetEndValue( set ); + savefile->ReadFloat( set ); + savefile->ReadFloat( set2 ); + alphaOn.SetHermiteParms(set, set2); + + savefile->ReadBool( wallwalkOn ); + savefile->ReadBool( flicker ); + savefile->ReadSkin( onSkin ); + savefile->ReadSkin( offSkin ); +} + +void hhWallWalkable::Think() { + idEntity::Think(); + if (thinkFlags & TH_THINK) { + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + if (alphaOn.IsDone(gameLocal.time)) { + BecomeInactive(TH_THINK); + if (wallwalkOn) { + } + else { + SetSkin( offSkin ); + } + } + } +} + +void hhWallWalkable::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(wallwalkOn, 1); + msg.WriteBits(IsActive(TH_THINK), 1); +} + +void hhWallWalkable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bool enabled = !!msg.ReadBits(1); + if (wallwalkOn != enabled) { + SetWallWalkable(enabled); + } + + bool thinking = !!msg.ReadBits(1); + if (thinking != IsActive(TH_THINK)) { + if (thinking) { + BecomeActive(TH_THINK); + } + else { + BecomeInactive(TH_THINK); + } + } +} + +void hhWallWalkable::ClientPredictionThink( void ) { + Think(); +} + +void hhWallWalkable::SetWallWalkable(bool on) { + wallwalkOn = on; + + float curAlpha = alphaOn.GetCurrentValue(gameLocal.time); + + if (wallwalkOn) { // Turning on + BecomeActive(TH_THINK); + SetSkin( onSkin ); + StartSound( "snd_powerup", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 1.0f ); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + } + else { // Turning off + BecomeActive(TH_THINK); + StartSound( "snd_powerdown", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 0.0f); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, 1.0f); // no overshoot + } +} + +void hhWallWalkable::Event_Activate(idEntity *activator) { + SetWallWalkable(!wallwalkOn); +} + +//========================================================================== +// +// hhFuncEmitter +// +//========================================================================== +CLASS_DECLARATION( idStaticEntity, hhFuncEmitter ) + EVENT( EV_Activate, hhFuncEmitter::Event_Activate ) +END_CLASS + +void hhFuncEmitter::Spawn( void ) { + particle = static_cast( declManager->FindType(DECL_PARTICLE, spawnArgs.GetString("smoke_particle"), false) ); + particleStartTime = -1; + + (spawnArgs.GetBool("start_off")) ? Hide() : Show(); +} + +void hhFuncEmitter::Hide() { + idStaticEntity::Hide(); + + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + + particleStartTime = -1; + + BecomeInactive( TH_TICKER ); +} + +void hhFuncEmitter::Show() { + idStaticEntity::Show(); + + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.GetTime() ); + + particleStartTime = gameLocal.GetTime(); + + BecomeActive( TH_TICKER ); +} + +void hhFuncEmitter::Save( idSaveGame *savefile ) const { + savefile->WriteParticle( particle ); + savefile->WriteInt( particleStartTime ); +} + +void hhFuncEmitter::Restore( idRestoreGame *savefile ) { + savefile->ReadParticle( particle ); + savefile->ReadInt( particleStartTime ); +} + +void hhFuncEmitter::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +void hhFuncEmitter::ReadFromSnapshot( const idBitMsgDelta &msg ) { + renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +void hhFuncEmitter::Ticker() { + if( IsHidden() ) { + return; + } + + if( particle && particleStartTime != -1 ) { + if( !gameLocal.smokeParticles->EmitSmoke(particle, particleStartTime, gameLocal.random.RandomFloat(), GetOrigin(), GetAxis()) ) { + particleStartTime = -1; + } + } + + if( modelDefHandle != -1 ) { + renderEntity.origin = GetOrigin(); + renderEntity.axis = GetAxis(); + UpdateVisuals(); + } +} + +void hhFuncEmitter::Event_Activate( idEntity *activator ) { + (IsHidden() || spawnArgs.GetBool("cycleTrigger")) ? Show() : Hide(); + + UpdateVisuals(); +} + + +//========================================================================== +// +// hhPathEmitter +// +//========================================================================== + +CLASS_DECLARATION( hhFuncEmitter, hhPathEmitter ) +END_CLASS + +void hhPathEmitter::Spawn() { +} + +//========================================================================== +// +// hhDeathWraithEnergy +// +//========================================================================== + +CLASS_DECLARATION( hhPathEmitter, hhDeathWraithEnergy ) +END_CLASS + +void hhDeathWraithEnergy::Spawn() { + startTime = MS2SEC(gameLocal.time); + duration = spawnArgs.GetFloat("duration"); + + startRadius = spawnArgs.GetFloat("startRadius"); + endRadius = spawnArgs.GetFloat("endRadius"); + startTheta = DEG2RAD(spawnArgs.GetFloat("startTheta")); + endTheta = DEG2RAD(spawnArgs.GetFloat("endTheta")); + startZ = spawnArgs.GetFloat("startZ"); + endZ = spawnArgs.GetFloat("endZ"); + + // For testing + SetDestination(vec3_origin); + SetPlayer(static_cast(gameLocal.GetLocalPlayer())); + + StartSound("snd_idle", SND_CHANNEL_BODY); +} + +void hhDeathWraithEnergy::Save(idSaveGame *savefile) const { + savefile->WriteFloat( startTime ); + savefile->WriteFloat( duration ); + savefile->WriteFloat( startRadius ); + savefile->WriteFloat( endRadius ); + savefile->WriteFloat( startTheta ); + savefile->WriteFloat( endTheta ); + savefile->WriteFloat( startZ ); + savefile->WriteFloat( endZ ); + savefile->WriteVec3( centerPosition ); + thePlayer.Save(savefile); + + savefile->WriteFloat(spline.tension); + savefile->WriteFloat(spline.continuity); + savefile->WriteFloat(spline.bias); + savefile->WriteInt(spline.nodes.Num()); // idList + for (int i=0; iWriteVec3(spline.nodes[i]); + } +} + +void hhDeathWraithEnergy::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( startRadius ); + savefile->ReadFloat( endRadius ); + savefile->ReadFloat( startTheta ); + savefile->ReadFloat( endTheta ); + savefile->ReadFloat( startZ ); + savefile->ReadFloat( endZ ); + savefile->ReadVec3( centerPosition ); + thePlayer.Restore(savefile); + + savefile->ReadFloat(spline.tension); + savefile->ReadFloat(spline.continuity); + savefile->ReadFloat(spline.bias); + + int num; + spline.nodes.Clear(); // idList + savefile->ReadInt(num); + spline.nodes.SetNum(num); + for (int i=0; iReadVec3(spline.nodes[i]); + } +} + +void hhDeathWraithEnergy::SetPlayer(hhPlayer *player) { + thePlayer = player; +} + +void hhDeathWraithEnergy::SetDestination(const idVec3 &destination) { + // Cylindrical support + this->centerPosition = destination; + idVec3 toWraith = GetOrigin() - centerPosition; + CartesianToCylindrical(toWraith, startRadius, startTheta, startZ); + endTheta += startTheta; + + // Spline support + spline.Clear(); + spline.SetControls(spawnArgs.GetFloat("tension"), spawnArgs.GetFloat("continuity"), spawnArgs.GetFloat("bias")); + spline.AddPoint(GetOrigin()); + + if (thePlayer.IsValid() && thePlayer->DeathWalkStage2()) { + idEntity *holeMarker = gameLocal.FindEntity( "dw_floatingBodyMarker" ); + if (holeMarker) { + spline.AddPoint(holeMarker->GetOrigin()); + } + } + + spline.AddPoint(destination); +} + +void hhDeathWraithEnergy::CartesianToCylindrical(idVec3 &cartesian, float &radius, float &theta, float &z) { + radius = cartesian.ToVec2().Length(); + theta = idMath::ATan(cartesian.y, cartesian.x); + z = cartesian.z; +} + +idVec3 hhDeathWraithEnergy::CylindricalToCartesian(float radius, float theta, float z) { + idVec3 cartesian; + cartesian.x = radius * idMath::Cos(theta); + cartesian.y = radius * idMath::Sin(theta); + cartesian.z = z; + return cartesian; +} + +void hhDeathWraithEnergy::Ticker() { + float theta; + float radius; + float z; + + if (!thePlayer.IsValid()) { + return; + } + + float alpha = (MS2SEC(gameLocal.time) - startTime) / duration; + + if (alpha < 1.0f) { + + if (thePlayer->DeathWalkStage2()) { + SetOrigin( spline.GetValue(alpha) ); + } + else { + radius = startRadius + alpha*(endRadius-startRadius); + theta = startTheta + alpha*(endTheta-startTheta); + z = startZ + alpha * (endZ - startZ); + + idVec3 locationRelativeToCenter = CylindricalToCartesian(radius, theta, z); + idEntity *destEntity = thePlayer->GetDeathwalkEnergyDestination(); + if (destEntity) { + centerPosition = destEntity->GetOrigin(); + } + + SetOrigin(centerPosition + locationRelativeToCenter); + } + } + else if (!IsHidden()) { + Hide(); + StopSound(SND_CHANNEL_BODY); + + bool energyHealth = spawnArgs.GetBool("healthEnergy"); + + idEntity *dwProxy = thePlayer->GetDeathwalkEnergyDestination(); + if (dwProxy) { + // Spawn arrival effect + StartSound("snd_arrival", SND_CHANNEL_ANY); + + dwProxy->SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time) ); + dwProxy->SetShaderParm(SHADERPARM_MODE, energyHealth ? 2 : 1 ); + } + + // Notify the player + thePlayer->DeathWraithEnergyArived(energyHealth); + + PostEventMS(&EV_Remove, 5000); + } + + hhPathEmitter::Ticker(); +} + + diff --git a/neo/prey/Prey/game_misc.h b/neo/prey/Prey/game_misc.h new file mode 100644 index 00000000..a8dba3d1 --- /dev/null +++ b/neo/prey/Prey/game_misc.h @@ -0,0 +1,100 @@ +#ifndef __HH_MISC_H +#define __HH_MISC_H + +#define WALLWALK_TRANSITION_TIME 2510 // Time over which to fade +#define WALLWALK_HERM_S1 1.0f // Slope at start +#define WALLWALK_HERM_S2 -2.0f // Slope at end + +class hhWallWalkable : public idStaticEntity { + CLASS_PROTOTYPE(hhWallWalkable); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void SetWallWalkable(bool on); + virtual void Think(); + +protected: + void Event_Activate(idEntity *activator); + +protected: + hhHermiteInterpolate alphaOn; // Degree to which wallwalk is on + bool wallwalkOn; + bool flicker; + const idDeclSkin *onSkin; + const idDeclSkin *offSkin; +}; + +class hhFuncEmitter : public idStaticEntity { + CLASS_PROTOTYPE( hhFuncEmitter ); + +public: + void Spawn( void ); + + virtual void Hide(); + virtual void Show(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + virtual void Ticker(); + +protected: + void Event_Activate( idEntity *activator ); + +protected: + const idDeclParticle* particle; + int particleStartTime; +}; + + +class hhPathEmitter : public hhFuncEmitter { + CLASS_PROTOTYPE( hhPathEmitter ); + +public: + void Spawn( void ); +}; + + +class hhDeathWraithEnergy : public hhPathEmitter { + CLASS_PROTOTYPE( hhDeathWraithEnergy ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPlayer(hhPlayer *player); + void SetDestination(const idVec3 &destination); + +protected: + virtual void Ticker(); + idVec3 CylindricalToCartesian(float radius, float theta, float z); + void CartesianToCylindrical(idVec3 &cartesian, float &radius, float &theta, float &z); + +protected: + hhTCBSpline spline; + float startTime; + float duration; + float startRadius; + float endRadius; + float startTheta; + float endTheta; + float startZ; + float endZ; + idVec3 centerPosition; + idEntityPtr thePlayer; +}; + +#endif diff --git a/neo/prey/Prey/game_modeldoor.cpp b/neo/prey/Prey/game_modeldoor.cpp new file mode 100644 index 00000000..4b42dbb0 --- /dev/null +++ b/neo/prey/Prey/game_modeldoor.cpp @@ -0,0 +1,1076 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// Events +const idEventDef EV_ModelDoorSpawnTrigger( "" ); +const idEventDef EV_ModelDoorOpen( "open" ); +const idEventDef EV_ModelDoorClose( "close" ); + +const idEventDef EV_ModelDoorClosedBegin( "" ); +const idEventDef EV_ModelDoorOpeningBegin( "" ); +const idEventDef EV_ModelDoorOpenBegin( "" ); +const idEventDef EV_ModelDoorClosingBegin( "" ); + +const idEventDef EV_SetBuddiesShaderParm( "setBuddiesShaderParm", "df" ); + +//-------------------------------- +// hhDoorTrigger +//-------------------------------- +CLASS_DECLARATION( idEntity, hhDoorTrigger ) + EVENT( EV_Touch, hhDoorTrigger::Event_TriggerDoor ) +END_CLASS + +//-------------------------------- +// hhDoorTrigger::hhDoorTrigger() +//-------------------------------- +hhDoorTrigger::hhDoorTrigger() { + enabled = true; // Need to have the ability to disable a trigger +} + +//-------------------------------- +// hhDoorTrigger::Event_TriggerDoor +//-------------------------------- +void hhDoorTrigger::Event_TriggerDoor( idEntity *other, trace_t *trace ) { + if( door && IsEnabled() ) { + door->ProcessEvent( &EV_Touch, other, trace ); + } +} + +//--------------------- +// hhDoorTrigger::GetEntitiesWithin +// ents should be an array of idEntity of length entLength +// +// If not enabled, always returns 0 +//--------------------- +int hhDoorTrigger::GetEntitiesWithin( idEntity **ents, int entsLength ) { + int num; + + if ( !enabled ) { + return( 0 ); + } + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, ents, entsLength ); + + return( num ); + +} + +void hhDoorTrigger::Save( idSaveGame *savefile ) const { + savefile->WriteObject( door ); + savefile->WriteBool( enabled ); +} + +void hhDoorTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast ( door ) ); + savefile->ReadBool( enabled ); +} + +//-------------------------------- +// hhModelDoor +//-------------------------------- +CLASS_DECLARATION( hhAnimatedEntity, hhModelDoor ) + EVENT( EV_TeamBlocked, hhModelDoor::Event_TeamBlocked ) + EVENT( EV_PartBlocked, hhModelDoor::Event_PartBlocked ) + EVENT( EV_Activate, hhModelDoor::Event_Activate ) + EVENT( EV_Touch, hhModelDoor::Event_Touch ) + EVENT( EV_ModelDoorOpen, hhModelDoor::Event_OpenDoor ) + EVENT( EV_ModelDoorClose, hhModelDoor::Event_CloseDoor ) + EVENT( EV_Thread_SetCallback, hhModelDoor::Event_SetCallback ) + + EVENT( EV_SetBuddiesShaderParm, hhModelDoor::Event_SetBuddiesShaderParm ) + + // Internal events + EVENT( EV_ModelDoorSpawnTrigger, hhModelDoor::Event_SpawnNewDoorTrigger ) + EVENT( EV_ModelDoorClosedBegin, hhModelDoor::Event_STATE_ClosedBegin ) + EVENT( EV_ModelDoorOpeningBegin, hhModelDoor::Event_STATE_OpeningBegin ) + EVENT( EV_ModelDoorOpenBegin, hhModelDoor::Event_STATE_OpenBegin ) + EVENT( EV_ModelDoorClosingBegin, hhModelDoor::Event_STATE_ClosingBegin ) +END_CLASS + +//-------------------------------- +// hhModelDoor::Spawn +//-------------------------------- +void hhModelDoor::Spawn( void ) { + idEntity* master = NULL; + + doorTrigger = NULL; + fl.takedamage = true; + SetBlocking(true); + +// spawnArgs.GetFloat( "dmg", "2", &damage ); + spawnArgs.GetFloat( "triggersize", "50", triggersize ); + spawnArgs.GetBool( "no_touch", "0", noTouch ); + spawnArgs.GetInt( "locked", "0", locked ); + spawnArgs.GetFloat( "wait", "1.5", wait ); + if ( wait <= 0.1f ) { + //sanity check for waittime + wait = 0.1f; + } + spawnArgs.GetFloat( "damage", "1", damage ); + spawnArgs.GetFloat( "airlockwait", "3.0", airLockSndWait ); + airLockSndWait *= 1000.0f; // Convert from seconds to ms + + hhUtils::GetValues( spawnArgs, "buddy", buddyNames, true ); + + // If "health" is supplied, door will open when killed + // So this key determines if we should really take damage or not + fl.takedamage = (health > 0); + forcedOpen = false; + bOpenForMonsters = spawnArgs.GetBool("OpenForMonsters", "1"); + + SetShaderParm( SHADERPARM_MODE, GetDoorShaderParm( locked != 0, true ) ); // 2=locked, 1=unlocked, 0=never locked + + //HUMANHEAD: aob - airlock stuff + airlockMaster = NULL; + + airlockTeam.SetOwner( this ); + airlockTeamName = spawnArgs.GetString( "airlockTeam" ); + master = DetermineTeamMaster( GetAirLockTeamName() ); + if( master ) { + airlockMaster = static_cast( master ); + if( airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + } + //HUMANHEAD END + + openAnim = GetAnimator()->GetAnim( "open" ); + closeAnim = GetAnimator()->GetAnim( "close" ); + idleAnim = GetAnimator()->GetAnim( "idle" ); + painAnim = GetAnimator()->GetAnim( "pain" ); + + // Spawn the trigger + if (!gameLocal.isClient) { + PostEventMS( &EV_ModelDoorSpawnTrigger, 0 ); + } + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + StartSound( "snd_idle", SND_CHANNEL_ANY ); + + if( spawnArgs.GetBool("start_open") ) { + StartOpen(); + } else { + StartClosed(); + } + + threadNum = 0; + nextAirLockSnd = 0; + + finishedSpawn = true; + + fl.networkSync = true; + + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); +} + +//-------------------------------- +// hhModelDoor::hhModelDoor +//-------------------------------- +hhModelDoor::hhModelDoor() { + bOpen = false; + bTransition = false; + finishedSpawn = false; + sndTrigger = NULL; + nextSndTriggerTime = 0; +} + +//-------------------------------- +// hhModelDoor::~hhModelDoor +//-------------------------------- +hhModelDoor::~hhModelDoor() { + airlockTeam.Remove(); + if ( sndTrigger ) { + delete sndTrigger; + sndTrigger = NULL; + } +} + +void hhModelDoor::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( damage ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( triggersize ); + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteStringList( buddyNames ); + savefile->WriteInt( locked ); + savefile->WriteInt( openAnim ); + savefile->WriteInt( closeAnim ); + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteBool( forcedOpen ); + savefile->WriteBool( bOpenForMonsters ); + savefile->WriteBool( noTouch ); + savefile->WriteInt( threadNum ); + savefile->WriteObject( doorTrigger ); + savefile->WriteString( airlockTeamName ); + savefile->WriteObject( airlockMaster ); + savefile->WriteBounds( crusherBounds ); + + activatedBy.Save( savefile ); + + savefile->WriteBool( bOpen ); + savefile->WriteBool( bTransition ); + savefile->WriteFloat( airLockSndWait ); + + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); +} + +void hhModelDoor::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( damage ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( triggersize ); + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadStringList( buddyNames ); + savefile->ReadInt( locked ); + savefile->ReadInt( openAnim ); + savefile->ReadInt( closeAnim ); + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadBool( forcedOpen ); + savefile->ReadBool( bOpenForMonsters ); + savefile->ReadBool( noTouch ); + savefile->ReadInt( threadNum ); + savefile->ReadObject( reinterpret_cast ( doorTrigger ) ); + savefile->ReadString( airlockTeamName ); + savefile->ReadObject( reinterpret_cast ( airlockMaster ) ); + savefile->ReadBounds( crusherBounds ); + + activatedBy.Restore( savefile ); + + savefile->ReadBool( bOpen ); + savefile->ReadBool( bTransition ); + savefile->ReadFloat( airLockSndWait ); + + airlockTeam.SetOwner( this ); + if( airlockMaster && airlockMaster != this ) { + JoinAirLockTeam( airlockMaster ); + } + + nextAirLockSnd = 0; + finishedSpawn = true; + bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" ); + + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); +} + +//-------------------------------- +// hhModelDoor::Lock +//-------------------------------- +void hhModelDoor::Lock( int f ) { + locked = f; + float parmValue = GetDoorShaderParm( locked != 0, false ); + SetShaderParm( SHADERPARM_MODE, parmValue ); + SetBuddiesShaderParm( SHADERPARM_MODE, parmValue ); + + if (locked) { + CloseDoor(); + } +} + +//-------------------------------- +// hhModelDoor::CanOpen +//-------------------------------- +bool hhModelDoor::CanOpen() const { + if( IsLocked() ) { + return false; + } + + if( !GetAirLockMaster() ) { + return true; + } + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && node->IsOpen() ) { + return false; + } + } + + return true; +} + +//-------------------------------- +// hhModelDoor::CanClose +//-------------------------------- +bool hhModelDoor::CanClose() const { + return true; +} + +//-------------------------------- +// hhModelDoor::OpenDoor +//-------------------------------- +void hhModelDoor::OpenDoor() { + if( IsClosed() && CanOpen() ) { + bTransition = true; + PostEventMS( &EV_ModelDoorOpeningBegin, 0 ); + } +} + +//-------------------------------- +// hhModelDoor::CloseDoor +//-------------------------------- +void hhModelDoor::CloseDoor() { + if( bOpen && !bTransition && CanClose() && !forcedOpen ) { + bTransition = true; + PostEventMS( &EV_ModelDoorClosingBegin, 0 ); + + // CJR: It's possible to open a door, then get back into it while it's animating closed, and then get squished + // the fix for this is to block the player from getting back in while it's closing. Projectiles will still pass through + GetPhysics()->SetContents( CONTENTS_PLAYERCLIP ); + } +} + +//-------------------------------- +// hhModelDoor::SetBlocking +//-------------------------------- +void hhModelDoor::SetBlocking( bool on ) { + GetPhysics()->SetContents( on ? CONTENTS_SOLID : 0 ); +} + +//-------------------------------- +// hhModelDoor::ClosePortal +//-------------------------------- +void hhModelDoor::ClosePortal( void ) { + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_VIEW ); + } +} + +//-------------------------------- +// hhModelDoor::OpenPortal +//-------------------------------- +void hhModelDoor::OpenPortal( void ) { + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } +} + + +//-------------------------------- +// hhModelDoor::InformDone +//-------------------------------- +void hhModelDoor::InformDone() { + idThread::ObjectMoveDone( threadNum, this ); + threadNum = 0; +} + +//-------------------------------- +// hhModelDoor::Damage +//-------------------------------- +void hhModelDoor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if (fl.takedamage) { + if ( !bOpen && painAnim ) { + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 500); + } + + hhAnimatedEntity::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); + } +} + +//-------------------------------- +// hhModelDoor::Killed +//-------------------------------- +void hhModelDoor::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (IsLocked()) { + Lock(0); + } + OpenDoor(); + fl.takedamage = false; + forcedOpen = true; +} + +//-------------------------------- +// hhModelDoor::SetBuddiesShaderParm +//-------------------------------- +void hhModelDoor::SetBuddiesShaderParm( int parm, float value ) { + idEntity* buddy = NULL; + + for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) { + if( !buddyNames[ix].Length() ) { + continue; + } + + buddy = gameLocal.FindEntity( buddyNames[ix].c_str() ); + if( !buddy ) { + continue; + } + + buddy->SetShaderParm( parm, value ); + } +} + +//-------------------------------- +// hhModelDoor::ToggleBuddiesShaderParm +//-------------------------------- +void hhModelDoor::ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay ) { + SetBuddiesShaderParm( parm, firstValue ); + + CancelEvents( &EV_SetBuddiesShaderParm ); + PostEventSec( &EV_SetBuddiesShaderParm, toggleDelay, parm, secondValue ); +} + +//-------------------------------- +// hhModelDoor::DetermineTeamMaster +//-------------------------------- +idEntity* hhModelDoor::DetermineTeamMaster( const char* teamName ) { + idEntity* ent = NULL; + + if ( teamName && teamName[0] ) { + // 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(hhModelDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + if (ent->IsType(hhDoor::Type) && !idStr::Icmp( static_cast(ent)->GetAirLockTeamName(), teamName )) { + return ent; + } + } + } + + return NULL; +} + +//-------------------------------- +// hhModelDoor::JoinAirLockTeam +//-------------------------------- +void hhModelDoor::JoinAirLockTeam( hhModelDoor *master ) { + assert( master ); + + airlockTeam.AddToEnd( master->airlockTeam ); +} + +//-------------------------------- +// hhModelDoor::VerifyAirlockTeamStatus +//-------------------------------- +void hhModelDoor::VerifyAirlockTeamStatus() { + if( !GetAirLockMaster() ) { + return; + } + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && !node->IsClosed() ) { + gameLocal.Warning( "Airlock team '%s' has more than one member starting open", GetAirLockTeamName() ); + } + } +} + +//-------------------------------- +// hhModelDoor::StartOpen +//-------------------------------- +void hhModelDoor::StartOpen() { + VerifyAirlockTeamStatus(); + + bTransition = false; + bOpen = true; + OpenPortal(); + SetBlocking(false); + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, openAnim, gameLocal.time, 500); + Event_STATE_OpenBegin(); +} + +//-------------------------------- +// hhModelDoor::StartClosed +//-------------------------------- +void hhModelDoor::StartClosed() { + bTransition = false; + bOpen = false; + Event_STATE_ClosedBegin(); +} + +//-------------------------------- +// hhModelDoor::Event_SpawnNewDoorTrigger +// +// All of the parts of a door have been spawned, so create +// a trigger that encloses all of them +//-------------------------------- +void hhModelDoor::Event_SpawnNewDoorTrigger( void ) { + idBounds bounds,localbounds; + idBounds triggerBounds, soundTriggerBounds; + int i; + int best; + + // Since models bounds are overestimated, we need to use the bounds from the + // clipmodel, which was set before the over-estimation + localbounds = GetPhysics()->GetBounds(); + + // Save the original bounds in case we need to crush something + crusherBounds = GetPhysics()->GetAbsBounds(); + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( localbounds[1][ i ] - localbounds[0][ i ] < localbounds[1][ best ] - localbounds[0][ best ] ) { + best = i; + } + } + triggerBounds = localbounds; + triggerBounds[1][ best ] += triggersize; + triggerBounds[0][ best ] -= triggersize; + + // Now transform into absolute coordintates + if ( GetPhysics()->GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( triggerBounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } + else { + bounds[0] = triggerBounds[0] + GetPhysics()->GetOrigin(); + bounds[1] = triggerBounds[1] + GetPhysics()->GetOrigin(); + } + + // create a trigger with this size + idDict args; + args.Set( "mins", bounds[0].ToString(0) ); + args.Set( "maxs", bounds[1].ToString(0) ); + + doorTrigger = ( hhDoorTrigger * ) gameLocal.SpawnEntityType( hhDoorTrigger::Type, &args ); + doorTrigger->GetPhysics()->SetContents( CONTENTS_TRIGGER ); + doorTrigger->door = this; + + // Disable the trigger if it is no_touch and not start_open + if ( noTouch ) { + doorTrigger->Disable(); + } + + + // ------------------------ + // Create the sound trigger + // ------------------------ + + float soundTriggerSize = triggersize * 0.3f; + soundTriggerBounds = localbounds; + soundTriggerBounds[1][ best ] += soundTriggerSize; + soundTriggerBounds[0][ best ] -= soundTriggerSize; + + // Now transform into absolute coordintates + if ( GetPhysics()->GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( soundTriggerBounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } + else { + bounds[0] = soundTriggerBounds[0] + GetPhysics()->GetOrigin(); + bounds[1] = soundTriggerBounds[1] + GetPhysics()->GetOrigin(); + } + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); + + // create a trigger clip model + sndTrigger = new idClipModel( idTraceModel( bounds ) ); + sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity ); + sndTrigger->SetContents( CONTENTS_TRIGGER ); + + // HACK: Also, since all the parts are now spawned, update the buddies' shaderparms + if (locked) { + float parmValue = GetDoorShaderParm( locked != 0, true ); + for ( i = 0; i < buddyNames.Num(); i++ ) { + if ( buddyNames[ i ].Length() ) { + idEntity *buddy = gameLocal.FindEntity( buddyNames[ i ] ); + if( buddy ) { + buddy->SetShaderParm(SHADERPARM_MODE, parmValue); + } + } + } + } + +} + +//-------------------------------- +// hhModelDoor::ToggleDoorState +//-------------------------------- +void hhModelDoor::ToggleDoorState( void ) { + if ( bOpen ) { + CloseDoor(); + } + else { + OpenDoor(); + } +} + +//-------------------------------- +// hhModelDoor::ForceAirLockTeamClosed +//-------------------------------- +void hhModelDoor::ForceAirLockTeamClosed() { + if( !GetAirLockMaster() ) { + return; + } + + ToggleBuddiesShaderParm( SHADERPARM_DIVERSITY, 1.0f, 0.0f, 2.0f ); + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && !node->IsClosed() ) { + node->CloseDoor(); + } + } +} + +//-------------------------------- +// hhModelDoor::ForceAirLockTeamOpen +//-------------------------------- +void hhModelDoor::ForceAirLockTeamOpen() { + if( !GetAirLockMaster() ) { + return; + } + + for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + if( node != this && !node->IsOpen() ) { + node->OpenDoor(); + } + } +} + +// State code +void hhModelDoor::Event_STATE_ClosedBegin() { + + // Alert any entities squished in the door + idEntity *touch[ MAX_GENTITIES ]; + int contentsMask = (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_TRIGGER); // corpses, moveableitems, spiritplayers, moveables + int num = hhUtils::EntitiesTouchingClipmodel(GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, contentsMask); + for (int ix=0; ix 0.0f ) { + // HUMANHEAD mdl + // Throw spirit players back to their bodies if they get caught in a closing door + // Don't check this for MP, the damage below is enough to send them back + if ( !gameLocal.isMultiplayer && ent->IsType( hhPlayer::Type ) ) { + hhPlayer *player = reinterpret_cast< hhPlayer * > ( ent ); + if ( player->IsSpiritWalking() ) { + player->StopSpiritWalk(); + continue; + } + } + + if( ent->fl.takedamage ) { + ent->Damage( this, this, vec3_origin, "damage_doorbonk", damage, INVALID_JOINT ); + } + } + + ent->SquishedByDoor(this); + } + } + + SetBlocking(true); + ClosePortal(); + InformDone(); + + if ( idleAnim ) { + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 500 ); + } + + // If we are noTouch, then disable the trigger. (Could have been enabled by GUI activation) + if ( noTouch && doorTrigger ) { + doorTrigger->Disable(); + } + + // Trigger entities if we have finished spawning + if ( !finishedSpawn ) { + return; + } + + ActivatePrefixed("triggerClosed", GetActivator()); + + if( GetAirLockMaster() ) { + // Mark the other team members as locked + for( hhModelDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as locked + node->SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } + } + + HH_ASSERT( bOpen == true && bTransition == true ); + bTransition = false; + bOpen = false; +} + +void hhModelDoor::Event_STATE_OpeningBegin() { + SetBlocking(false); + OpenPortal(); + StartSound( "snd_open", SND_CHANNEL_ANY ); + + // Fire any triggerStartOpen entities + ActivatePrefixed("triggerStartOpen", GetActivator()); + + idEntity *ent; + idEntity *next; + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent && ent->IsType( hhProjectile::Type ) ) { + ent->Unbind(); // bjk drops all bound projectiles such as arrows and mines + next = teamChain; + if (ent->IsType(hhProjectileSpiritArrow::Type)) { + ent->PostEventMS(&EV_Remove, 0); + } + //HUMANHEAD bjk PCF (4-28-06) - explode crawlers + if (ent->IsType(hhProjectileStickyCrawlerGrenade::Type)) { + static_cast(ent)->PostEventSec( &EV_Explode, 0.2f ); + } + } + } + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, openAnim, gameLocal.time, 500); + int opentime = (openAnim) ? GetAnimator()->GetAnim( openAnim )->Length() : 0; + PostEventMS( &EV_ModelDoorOpenBegin, opentime ); + + if( airlockMaster ) { + // Mark the other team members as locked + for( hhModelDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) { + + // Skip this door and it's buddies + if( node == this || buddyNames.Find( node->name ) ) { + continue; + } + + // Don't change doors that are really locked + if( node->IsLocked() ) { + continue; + } + + // Mark as locked + node->SetBuddiesShaderParm( SHADERPARM_MISC, 1.0f ); + } + + // This prevents the our shader from being set as locked from the player moving too quickly between airlock doors + SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f ); + } + + HH_ASSERT( bOpen == false && bTransition == true ); +} + +void hhModelDoor::Event_STATE_OpenBegin() { + InformDone(); + + // If we are no touch, then we were opened by something else, so let us care about the player being in the trigger/enable the trigger + if ( noTouch && doorTrigger ) { + doorTrigger->Enable(); + } + + // Trigger entities if we have finished spawning + if ( !finishedSpawn ) { + return; + } + + ActivatePrefixed("triggerOpened", GetActivator()); + + WakeTouchingEntities(); + + HH_ASSERT( bOpen == false && bTransition == true ); + bOpen = true; + bTransition = false; +} + +void hhModelDoor::Event_STATE_ClosingBegin() { + StartSound("snd_close", SND_CHANNEL_ANY); + + //GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + //FIXME: Despite the blend in/out here, it still pops when the idle is reapplied + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, closeAnim, gameLocal.time, 500); + int closetime = (closeAnim) ? GetAnimator()->GetAnim( closeAnim )->Length() : 0; + PostEventMS( &EV_ModelDoorClosedBegin, closetime ); + + HH_ASSERT( bOpen == true && bTransition == true ); +} + +// ------------------------------------------------------------- +// Non-state code +// ------------------------------------------------------------- + +//-------------------------------- +// hhModelDoor::Event_Blocked +//-------------------------------- +void hhModelDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + // reverse direction + ToggleDoorState(); +} + +//-------------------------------- +// hhModelDoor::Event_PartBlocked +//-------------------------------- +void hhModelDoor::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_doorbonk", 1.0f, INVALID_JOINT ); + } +} + +//-------------------------------- +// hhModelDoor::Event_Activate +//-------------------------------- +void hhModelDoor::Event_Activate( idEntity *activator ) { + idEntity *buddy = NULL; + + if ( IsLocked() ) { + int oldLocked = locked; + Lock(0); + if ( oldLocked == 2 ) { + return; + } + + ToggleDoorState(); + CancelEvents( &EV_ModelDoorClose ); + PostEventMS( &EV_ModelDoorClose, wait * 1000 ); + } + else { + TryOpen( activator ); + } + +} + + +//-------------------------------- +// hhModelDoor::WakeTouchingEntities +//-------------------------------- +void hhModelDoor::WakeTouchingEntities() { + idEntity *touch[ MAX_GENTITIES ]; + idEntity *ent; + int num; + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_ALL, touch, MAX_GENTITIES ); + for ( int i = 0; i < num; i++ ) { + ent = touch[ i ]; + if ( !ent || ent == this ) { + continue; + } + ent->ActivatePhysics(this); + } +} + +//-------------------------------- +// hhModelDoor::TryOpen +// If conditions are right, open. Otherwise don't open +//-------------------------------- +void hhModelDoor::TryOpen( idEntity *whoTrying ) { + + activatedBy = whoTrying; // Should thie be above OpenDoor + + // Only allow the player to affect airlock doors + if ( GetAirLockMaster() && !whoTrying->IsType( idPlayer::Type ) ) { + return; + } + + // Check if this actor is valid to open this door (if the player is spiritwalking) - cjr + if ( !whoTrying->ShouldTouchTrigger( this ) ) { + return; + } + + // If we're shuttle doors, make sure we only open for actors in vehicles. + if ( bShuttleDoors && ( ! whoTrying->IsType( idActor::Type ) || ! reinterpret_cast ( whoTrying )->InVehicle() ) ) { + return; + } + + if ( !bOpenForMonsters && whoTrying->IsType( hhMonsterAI::Type ) ) { + return; + } + + // Delay Close event since we're still in the doorway + CancelEvents( &EV_ModelDoorClose ); + PostEventMS( &EV_ModelDoorClose, wait * 1000 ); + + //This feels like ahack but I need to get this done + if( IsClosed() && !CanOpen() && GetAirLockMaster() ) { + ForceAirLockTeamClosed(); + if( gameLocal.time > nextAirLockSnd ) { + int length; + StartSound( "snd_airlock", SND_CHANNEL_BODY, 0, false, &length ); + nextAirLockSnd = gameLocal.time + length + airLockSndWait; + } + } + OpenDoor(); +} + + +//-------------------------------- +// hhModelDoor::EntitiesInTrigger +//-------------------------------- +bool hhModelDoor::EntitiesInTrigger() { + idEntity * ents[ MAX_GENTITIES ]; + idEntity * ent; + int num; + + if ( !doorTrigger ) { + return( false ); + } + + num = doorTrigger->GetEntitiesWithin( ents, MAX_GENTITIES ); + for ( int i = 0; i < num; ++i ) { + ent = ents[ i ]; + + if ( ent->fl.touchTriggers && ent->ShouldTouchTrigger( this ) ) { + + if ( !bOpenForMonsters && ent->IsType( hhMonsterAI::Type ) ) { + continue; + } + + if ( bShuttleDoors && ( ! ent->IsType( idActor::Type ) || ! reinterpret_cast ( ent )->InVehicle() ) ) { + continue; + } + + if ( !GetAirLockMaster() || ent->IsType( idPlayer::Type ) ) { // Only allow the player to affect airlock doors + return( true ); + } + } + } + + return( false ); +} + + +//-------------------------------- +// hhModelDoor::Event_Touch +//-------------------------------- +void hhModelDoor::Event_Touch( idEntity *other, trace_t* trace ) { + + if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { + if (other && other->IsType(hhPlayer::Type) && IsLocked() && gameLocal.time > nextSndTriggerTime) { + StartSound("snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + return; + } + + // Skip if locked, or noTouch + if( IsLocked() || noTouch ) { + // play locked sound + return; + } + + TryOpen( other ); +} + + +/* +================ +idModelDoor::GetActivator +================ +*/ +idEntity *hhModelDoor::GetActivator( void ) const { + return activatedBy.GetEntity(); +} + +/* +================ +hhModelDoor::ClientPredictionThink +================ +*/ +void hhModelDoor::ClientPredictionThink( void ) { + hhAnimatedEntity::ClientPredictionThink(); +} + +/* +================ +hhModelDoor::WriteToSnapshot +================ +*/ +void hhModelDoor::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhAnimatedEntity::WriteToSnapshot(msg); + hhAnimator *animator = (hhAnimator *)GetAnimator(); + const idAnimBlend *anim = animator->CurrentAnim(ANIMCHANNEL_ALL); + if (anim) { + msg.WriteBits(1, 1); + msg.WriteBits(anim->AnimNum(), 32); + } + else { + msg.WriteBits(0, 1); + } + + msg.WriteBits(GetPhysics()->GetContents(), 32); +} + +/* +================ +hhModelDoor::ReadFromSnapshot +================ +*/ +void hhModelDoor::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhAnimatedEntity::ReadFromSnapshot(msg); + + bool hasAnim = !!msg.ReadBits(1); + if (hasAnim) { + int animNum = msg.ReadBits(32); + hhAnimator *animator = (hhAnimator *)GetAnimator(); + const idAnimBlend *anim = animator->CurrentAnim(ANIMCHANNEL_ALL); + if (!anim || anim->AnimNum() != animNum) { + animator->PlayAnim(ANIMCHANNEL_ALL, animNum, gameLocal.time, 500); + } + } + + int contents = msg.ReadBits(32); + if (contents != GetPhysics()->GetContents()) { + GetPhysics()->SetContents(contents); + } +} + + +//-------------------------------- +// hhModelDoor::Event_ToggleDoorState +//-------------------------------- +void hhModelDoor::Event_ToggleDoorState( void ) { + ToggleDoorState(); +} + +//-------------------------------- +// hhModelDoor::Event_OpenDoor +//-------------------------------- +void hhModelDoor::Event_OpenDoor() { + OpenDoor(); +} + +//-------------------------------- +// hhModelDoor::Event_CloseDoor +//-------------------------------- +void hhModelDoor::Event_CloseDoor() { + + // Added due to the fact that monsters which don't move, also don't ActivateTriggers. + // So now we check before we close if we can actually close + + // We can't actually close if are transitioning or if we have someone in our trigger that we care about + if ( bTransition || EntitiesInTrigger() ) { + // So if someone here, cancel any further events + CancelEvents( &EV_ModelDoorClose ); + // And try again in a bit. + PostEventMS( &EV_ModelDoorClose, wait * 1000 ); + return; + } + + CloseDoor(); +} + +//-------------------------------- +// hhModelDoor::Event_SetCallback +//-------------------------------- +void hhModelDoor::Event_SetCallback( void ) { + if ( !threadNum && bTransition ) { + threadNum = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +//-------------------------------- +// hhModelDoor::Event_SetBuddiesShaderParm +//-------------------------------- +void hhModelDoor::Event_SetBuddiesShaderParm( int parm, float value ) { + SetBuddiesShaderParm( parm, value ); +} diff --git a/neo/prey/Prey/game_modeldoor.h b/neo/prey/Prey/game_modeldoor.h new file mode 100644 index 00000000..4566c5b0 --- /dev/null +++ b/neo/prey/Prey/game_modeldoor.h @@ -0,0 +1,139 @@ +#ifndef __GAME_MODELDOOR_H__ +#define __GAME_MODELDOOR_H__ + +extern const idEventDef EV_ModelDoorOpen; +extern const idEventDef EV_ModelDoorClose; + +extern const idEventDef EV_SetBuddiesShaderParm; + +class hhDoorTrigger; // Stupid C++ forward decl for hhModelDoor + +//-------------------------------- +// hhModelDoor +//-------------------------------- +class hhModelDoor : public hhAnimatedEntity { + +public: + CLASS_PROTOTYPE( hhModelDoor ); + + hhModelDoor(); + virtual ~hhModelDoor(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // Overridden methods + 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 ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void ToggleDoorState( void ); + void OpenDoor(); + void CloseDoor(); + void Lock( int f ); + bool IsLocked() const { return locked != 0; } + ID_INLINE bool IsOpen() const { return ( bOpen || bTransition ); } + ID_INLINE bool IsClosed() const { return ( !bOpen && !bTransition ); } + bool CanOpen() const; + bool CanClose() const; + void ForceAirLockTeamClosed(); + void ForceAirLockTeamOpen(); + const char* GetAirLockTeamName() const { return airlockTeamName.c_str(); } + hhModelDoor* GetAirLockMaster() const { return airlockMaster; } + idEntity * GetActivator() const; + + idLinkList airlockTeam; + +protected: + void SetBlocking( bool on ); + void ClosePortal( void ); + void OpenPortal( void ); + void InformDone(); + + void SetBuddiesShaderParm( int parm, float value ); + void ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay ); + void WakeTouchingEntities(); + + idEntity* DetermineTeamMaster( const char* teamName ); + void JoinAirLockTeam( hhModelDoor *master ); + void VerifyAirlockTeamStatus(); + + void StartOpen(); + void StartClosed(); + void TryOpen( idEntity *whoTrying ); + bool EntitiesInTrigger(); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_SpawnNewDoorTrigger( void ); + void Event_SetCallback( void ); + void Event_SetBuddiesShaderParm( int parm, float value ); + void Event_Touch( idEntity *other, trace_t* trace ); + void Event_Activate( idEntity *activator ); + void Event_ToggleDoorState( void ); + void Event_OpenDoor(); + void Event_CloseDoor(); + void Event_STATE_ClosedBegin(); + void Event_STATE_OpeningBegin(); + void Event_STATE_OpenBegin(); + void Event_STATE_ClosingBegin(); + +protected: + float damage; + float wait; + float triggersize; + qhandle_t areaPortal; // 0 = no portal + idList buddyNames; + int locked; + int openAnim; + int closeAnim; + int idleAnim; + int painAnim; + bool forcedOpen; // Is the door forced open + bool bOpenForMonsters; // Door opens for monsters + bool noTouch; // Can you touch this door. + bool bOpen; // HUMANHEAD mdl: True if door is open + bool bTransition; // HUMANHEAD mdl: True if door is in transition between opening and closing + bool bShuttleDoors; + int threadNum; // Thread used for sys.WaitFor() calls + hhDoorTrigger * doorTrigger; + idClipModel * sndTrigger; + float airLockSndWait; // Time in milliseconds between airlock sounds from spawnarg airlockwait + float nextAirLockSnd; // Next time to play an airlock locked door sound + int nextSndTriggerTime; // next time to play door locked sound + + idStr airlockTeamName; + hhModelDoor *airlockMaster; + + idBounds crusherBounds; + + idEntityPtr activatedBy; + + bool finishedSpawn; // Used to determine if we are still spawning in or not. + +}; + +class hhDoorTrigger : public idEntity { + CLASS_PROTOTYPE( hhDoorTrigger ); + +public: + void Disable() { enabled = false; } + void Enable() { enabled = true; } + bool IsEnabled() { return( enabled ); } + int GetEntitiesWithin( idEntity **ents, int entsLength ); + + hhModelDoor *door; + bool enabled; + +protected: + hhDoorTrigger(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_TriggerDoor( idEntity *other, trace_t *trace ); +}; + +#endif /* __GAME_MODELDOOR_H__ */ diff --git a/neo/prey/Prey/game_modeltoggle.cpp b/neo/prey/Prey/game_modeltoggle.cpp new file mode 100644 index 00000000..378c1d3a --- /dev/null +++ b/neo/prey/Prey/game_modeltoggle.cpp @@ -0,0 +1,316 @@ +/*********************************************************************** + hhModelToggle + + Usage: + Set target to a hhViewedModel entity that will change model. + + operations: + next/prev model + next/prev anim + toggle rotation + toggle translation + toggle cycle + +***********************************************************************/ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +// hhViewedModel ----------------------------------------------------------- + +CLASS_DECLARATION(hhAnimatedEntity, hhViewedModel) +END_CLASS + +void hhViewedModel::Spawn(void) { + + physicsObj.SetSelf(this); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + SetPhysics( &physicsObj ); + + rotationAmount = 0; + BecomeActive(TH_THINK|TH_TICKER); +} + +void hhViewedModel::SetRotationAmount(float amount) { + rotationAmount = amount; +} + +void hhViewedModel::Ticker() { + idAngles ang; + + // update rotation + physicsObj.GetAngles( ang ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, gameLocal.msec, ang, idAngles( 0, rotationAmount * 360.0f / 60.0f, 0 ), ang_zero ); + + // update visuals so that the skeleton is drawn for non-rotating models + UpdateVisuals(); +} + + +// hhModelToggle ------------------------------------------------------------ + +const idEventDef EV_SetInitialAnims("", NULL); +const idEventDef EV_RequireTargets("", NULL); + +CLASS_DECLARATION(hhConsole, hhModelToggle) + EVENT( EV_SetInitialAnims, hhModelToggle::Event_SetInitialAnims ) + EVENT( EV_RequireTargets, hhModelToggle::Event_RequireTargets ) +END_CLASS + + +void hhModelToggle::Spawn(void) { + const idKeyValue *arg; + int i, num; + + bTranslation = false; + bRotation = false; + bCycle = true; + + // Retrieve list of definitions + defList.Clear(); + num = spawnArgs.GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + arg = spawnArgs.GetKeyVal( i ); + if ( !arg->GetKey().Icmpn( "defentity", 9 ) ) { + defList.Append(arg->GetValue()); + } + } + + FillResourceList(); + BecomeActive(TH_THINK); + + PostEventMS(&EV_RequireTargets, 0); // Target list not built until after Spawn() + PostEventMS(&EV_SetInitialAnims, 0); // Can't set anims in Spawn() +} + +void hhModelToggle::FillResourceList() { + const idDict *dict; + ResourceSet set; + int i; + const idKeyValue *kv; + + for (i=0; iFindKey("model_view"); + if (!kv) { + kv = dict->FindKey("model"); + if (!kv) { + gameLocal.Warning("No model for %s", defList[i].c_str()); + continue; + } + } + + set.model = kv->GetValue(); + set.animList.Clear(); + set.animFileList.Clear(); + set.args = dict; + + const idDeclModelDef *modelDef = static_cast( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) ); + if (modelDef) { + int num = modelDef->NumAnims(); + for (int i=1; iGetAnim(i); + idStr animName = anim->FullName(); + set.animList.Append(animName); + idStr animFileName = anim->MD5Anim(0)->Name(); + set.animFileList.Append(animFileName); + } + } + else { + kv = NULL; + while (1) { + kv = dict->MatchPrefix("anim ", kv); + if (!kv) { + break; + } + idStr animName = kv->GetKey().c_str() + 5; + set.animList.Append(animName); + set.animFileList.Append(kv->GetValue()); + } + } + + if (set.animList.Num() == 0) { + gameLocal.Warning("No anims for %s", defList[i].c_str()); + continue; + } + + resources.Append(set); + } +} + +void hhModelToggle::UpdateGUIValues() { + if (renderEntity.gui[0]) { + renderEntity.gui[0]->SetStateString("modelname", resources[currentModel].model.c_str()); + if (resources[currentModel].animList.Num() > 0) { + renderEntity.gui[0]->SetStateString("animname", resources[currentModel].animList[currentAnim].c_str()); + renderEntity.gui[0]->SetStateString("animfile", resources[currentModel].animFileList[currentAnim].c_str()); + } + renderEntity.gui[0]->SetStateInt("translation", bTranslation); + renderEntity.gui[0]->SetStateInt("rotation", bRotation); + renderEntity.gui[0]->SetStateInt("cycle", bCycle); + } +} + +void hhModelToggle::SetTargetsToModel(const char *modelname) { + for (int t=0; tSetModel(modelname); + } + } + + UpdateGUIValues(); +} + +void hhModelToggle::SetTargetsToAnim(const char *animname) { + for (int t=0; tGetAnimator()->GetAnim(animname); + + ent->GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + + if (bCycle) { + ent->GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, anim, gameLocal.time, 0); + } + else { + ent->GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, anim, gameLocal.time, 0); + } + + ent->GetAnimator()->RemoveOriginOffset(!bTranslation); + } + } + + UpdateGUIValues(); +} + +void hhModelToggle::SetTargetsToRotate(bool bRotate) { + for (int t=0; tIsType(hhViewedModel::Type)) { + static_cast(ent)->SetRotationAmount(bRotate?5:0); + } + } + } + + UpdateGUIValues(); +} + + +void hhModelToggle::NextModel(void) { + if (resources.Num() > 0) { + currentModel = (currentModel + 1) % resources.Num(); + currentAnim = 0; + + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +void hhModelToggle::PrevModel(void) { + if (resources.Num() > 0) { + currentModel = (currentModel - 1 + resources.Num()) % resources.Num(); + currentAnim = 0; + + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +void hhModelToggle::NextAnim(void) { + int numanims = resources[currentModel].animList.Num(); + if (numanims > 0) { + currentAnim = (currentAnim + 1) % numanims; + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +void hhModelToggle::PrevAnim(void) { + int numanims = resources[currentModel].animList.Num(); + if (numanims > 0) { + currentAnim = (currentAnim - 1 + numanims) % numanims; + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } +} + +bool hhModelToggle::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("nextmodel") == 0) { + NextModel(); + return true; + } + else if (token.Icmp("prevmodel") == 0) { + PrevModel(); + return true; + } + else if (token.Icmp("nextanim") == 0) { + NextAnim(); + return true; + } + else if (token.Icmp("prevanim") == 0) { + PrevAnim(); + return true; + } + else if (token.Icmp("togglerotation") == 0) { + // Instead, have gui call a script function and handle rotation with a mover + bRotation ^= 1; + SetTargetsToRotate(bRotation); + return true; + } + else if (token.Icmp("toggletranslation") == 0) { + bTranslation ^= 1; + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + return true; + } + else if (token.Icmp("togglecycle") == 0) { + bCycle ^= 1; + SetTargetsToModel(resources[currentModel].model.c_str()); + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + return true; + } + + src->UnreadToken(&token); + return false; +} + +void hhModelToggle::Event_SetInitialAnims() { + // Set to currentModel, currentAnim + currentModel = currentAnim = 0; + if (resources.Num() > 0) { + SetTargetsToModel(resources[currentModel].model.c_str()); + if (resources[currentModel].animList.Num() > 0) { + SetTargetsToAnim(resources[currentModel].animList[currentAnim].c_str()); + } + } +} + +void hhModelToggle::Event_RequireTargets() { + // Require a valid target: must be delayed after spawn because targets aren't build until then + if (!targets.Num()) { + gameLocal.Error( "ModelViewer requires a valid target." ); + PostEventMS(&EV_Remove, 0); + } +} + diff --git a/neo/prey/Prey/game_modeltoggle.h b/neo/prey/Prey/game_modeltoggle.h new file mode 100644 index 00000000..9c3a3754 --- /dev/null +++ b/neo/prey/Prey/game_modeltoggle.h @@ -0,0 +1,62 @@ +#ifndef __GAME_MODELTOGGLE_H__ +#define __GAME_MODELTOGGLE_H__ + +typedef struct ResourceSet_s{ + idStr model; + idList animList; + idList animFileList; + const idDict * args; +} ResourceSet; + + +class hhViewedModel : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhViewedModel ); + + void Spawn(); + void SetRotationAmount(float amount); + + idPhysics_Parametric physicsObj; + float rotationAmount; + +protected: + virtual void Ticker(); +}; + + +class hhModelToggle : public hhConsole { +public: + CLASS_PROTOTYPE( hhModelToggle ); + + void Spawn( void ); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void NextModel(void); + void PrevModel(void); + void NextAnim(void); + void PrevAnim(void); + void SetTargetsToModel(const char *modelname); + void SetTargetsToAnim(const char *animname); + void FillResourceList(); + void UpdateGUIValues(); + void Update(); + void SetTargetsToRotate(bool bRotate); + + void Event_SetInitialAnims(); + void Event_RequireTargets(); + +protected: + idList defList; + idList resources; + + int currentModel; + int currentAnim; + bool bTranslation; + bool bRotation; + bool bCycle; + +}; + + +#endif /* __GAME_MODELTOGGLE_H__ */ diff --git a/neo/prey/Prey/game_monster_ai.cpp b/neo/prey/Prey/game_monster_ai.cpp new file mode 100644 index 00000000..f85eeb27 --- /dev/null +++ b/neo/prey/Prey/game_monster_ai.cpp @@ -0,0 +1,2158 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------- +// hhNoClipEnt +//----------------------------------------------------- +CLASS_DECLARATION(idEntity, hhNoClipEnt) +END_CLASS + +void hhNoClipEnt::Spawn( void ) { + if(GetPhysics()) + GetPhysics()->SetContents(0); +} + +//----------------------------------------------------- +// hhAINode +//----------------------------------------------------- +CLASS_DECLARATION( idEntity, hhAINode ) +END_CLASS + +hhAINode::hhAINode( void ) { + user.Clear(); +} + +void hhAINode::Save( idSaveGame *savefile ) const { + user.Save( savefile ); +} + +void hhAINode::Restore( idRestoreGame *savefile ) { + user.Restore( savefile ); +} + +//----------------------------------------------------- +// hhMonsterAI +//----------------------------------------------------- + +idList hhMonsterAI::allSimpleMonsters; + +hhMonsterAI::hhMonsterAI() { + lookOffset = ang_zero; + shootTarget = NULL; + bBossBar = false; + spawnThinkFlags = 0; + lastContactTime = gameLocal.time; + nextSpeechTime = gameLocal.time; + frozen = false; + soundOnModel = false; +} + +hhMonsterAI::~hhMonsterAI() { + allSimpleMonsters.Remove(this); + + if ( bBossBar && spawnArgs.GetBool( "remove_bar_on_dissolve" ) ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + bBossBar = false; + player->hud->HandleNamedEvent("HideProgressBar"); + player->hud->StateChanged(gameLocal.time); + } + } +} + +void hhMonsterAI::Spawn() { + AI_HAS_RANGE_ATTACK = spawnArgs.GetBool("has_range_attack", "0"); + AI_HAS_MELEE_ATTACK = spawnArgs.GetBool("has_melee_attack", "0"); + + targetReaction.reactionIndex = -1; + targetReaction.entity = NULL; + + allSimpleMonsters.AddUnique(this); + bBindAxis = spawnArgs.GetBool( "bind_axis" ); + bCanFall = spawnArgs.GetBool( "can_fall", "0" ); + bSeeThroughPortals = spawnArgs.GetBool( "can_see_portals", "0" ); + bBindOrient = spawnArgs.GetBool( "bind_orient", "0" ); + hearingRange = spawnArgs.GetInt( "hearing_range", "1024" ); + bCanWallwalk = spawnArgs.GetBool( "can_wallwalk", "0" ); + fallDelay = spawnArgs.GetInt( "fall_delay" ); + bOverrideKilledByGravityZones = spawnArgs.GetBool( "overrideKilledByGravityZones" ); + bNeverTarget = spawnArgs.GetBool( "never_target", "0" ); + bNoCombat = spawnArgs.GetBool( "no_combat", "0" ); + const char *temp; + if ( spawnArgs.GetString( "fx_custom_blood", "", &temp ) ) { + bCustomBlood = true; + } else { + bCustomBlood = false; + } + + nextSpiritProxyCheck = 0; + nextTurnUpdate = 0; + frozen = false; + + //handle initial rotation and sticking on wallwalk + if ( !IsHidden() ) { + PostEventSec( &MA_InitialWallwalk, 0.1f ); + PostEventMS(&MA_EnemyOnSpawn, 10); + } + PostEventMS(&EV_PostSpawn, 0); + if ( spawnArgs.GetInt( "wander_radius" ) ) { + spawnOrigin = GetOrigin(); + } + + // CJR: Clear the DDA values used for tracking damage inflicted on the player + totalDDADamage = 0; +} + +void hhMonsterAI::Event_PostSpawn() { + CreateHealthTriggers(); + + //TODO: Move to character/girlfriend code when it exists + // Spawn earrings for girlfriend + idEntity *ent; + const char *defName = spawnArgs.GetString("def_earring", NULL); + if (defName && defName[0] && head.IsValid()) { + const char *boneNameL = spawnArgs.GetString("earringboneL"); + const char *boneNameR = spawnArgs.GetString("earringboneR"); + idDict args; + args.Clear(); + args.Set( "origin", GetPhysics()->GetOrigin().ToString() ); + + ent = gameLocal.SpawnObject(defName, &args); + if (ent) { + ent->MoveToJoint(head.GetEntity(), boneNameL); + ent->BindToJoint(head.GetEntity(), boneNameL, false); + } + + ent = gameLocal.SpawnObject(defName, &args); + if (ent) { + ent->MoveToJoint(head.GetEntity(), boneNameR); + ent->BindToJoint(head.GetEntity(), boneNameR, false); + } + } +} + + +bool hhMonsterAI::HasPathTo(const idVec3 &destPt) { + if(!GetAAS()) // || !allowAAS) //TODO: support the allowAAS functionality + return FALSE; + + idVec3 fromPos = GetPhysics()->GetOrigin(); + + int currAreaNum = PointReachableAreaNum(fromPos); + int toAreaNum = PointReachableAreaNum(destPt); + + + // Invalid #'s ? + if(toAreaNum == 0 || currAreaNum == 0) + return FALSE; + + aasPath_t path; + return PathToGoal(path, currAreaNum, fromPos, toAreaNum, destPt); +} + +/* +============================== +hhMonsterAI::LinkScriptVariables(void) +============================== +*/ +#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name ) +void hhMonsterAI::LinkScriptVariables(void) { + + idAI::LinkScriptVariables(); + + LinkScriptVariable( AI_HAS_RANGE_ATTACK ); // required for reaction system + LinkScriptVariable( AI_HAS_MELEE_ATTACK ); // required for reaction system + LinkScriptVariable( AI_USING_REACTION ); // TRUE if monster is currently using reaction + LinkScriptVariable( AI_REACTION_FAILED ); // TRUE if the last reaction attempt failed (path blocked, exclusive problem, etc) + LinkScriptVariable( AI_REACTION_ANIM ); + LinkScriptVariable( AI_BACKWARD ); + LinkScriptVariable( AI_STRAFE_LEFT ); + LinkScriptVariable( AI_STRAFE_RIGHT ); + LinkScriptVariable( AI_UPWARD ); + LinkScriptVariable( AI_DOWNWARD ); + LinkScriptVariable( AI_SHUTTLE_DOCKED ); + LinkScriptVariable( AI_VEHICLE_ATTACK ); + LinkScriptVariable( AI_VEHICLE_ALT_ATTACK ); + LinkScriptVariable( AI_WALLWALK ); + LinkScriptVariable( AI_FALLING ); + LinkScriptVariable( AI_PATHING ); + LinkScriptVariable( AI_TURN_DIR ); + LinkScriptVariable( AI_FLY_NO_SEEK ); + LinkScriptVariable( AI_FOLLOWING_PATH ); +} + +void hhMonsterAI::Think( void ) { + PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI); + if (ai_skipThink.GetBool()) { //HUMANHEAD rww + return; + } + + idVec3 oldOrigin = physicsObj.GetOrigin(); + idVec3 oldVelocity = physicsObj.GetLinearVelocity(); + + if ( thinkFlags & TH_THINK ) { + + // Update vehicle guns + if(AI_VEHICLE && InVehicle()) { + usercmd_t cmds; + const signed char speed = 64; // In range [0..127] + memset( &cmds, 0, sizeof(usercmd_t) ); + cmds.buttons |= (AI_VEHICLE_ATTACK) ? BUTTON_ATTACK : 0; + cmds.buttons |= (AI_VEHICLE_ALT_ATTACK) ? BUTTON_ATTACK_ALT : 0; + + if(AI_FORWARD) { + cmds.forwardmove = speed; + } else if(AI_BACKWARD) { + cmds.forwardmove = -speed; + } + + if(AI_STRAFE_RIGHT) { + cmds.rightmove = speed; + } else if(AI_STRAFE_LEFT) { + cmds.rightmove = -speed; + } + + if(AI_UPWARD) { + cmds.upmove = speed; + } else if(AI_DOWNWARD) { + cmds.upmove = -speed; + } + + GetVehicleInterfaceLocal()->BufferPilotCmds( &cmds, NULL ); + AI_SHUTTLE_DOCKED = GetVehicleInterfaceLocal()->IsVehicleDocked(); + } + + // clear out the enemy when he dies or is hidden + idActor *enemyEnt = enemy.GetEntity(); + if ( enemyEnt ) { + if ( enemyEnt->IsType( hhSpiritProxy::Type ) ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->AI_DEAD ) { + EnemyDead(); + } + } else if ( enemyEnt->health <= 0 ) { + EnemyDead(); + } + } + + //HUMANHEAD: aob - vehicle updates our viewAxis + if( !InVehicle() ) { + current_yaw += deltaViewAngles.yaw; + ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); + + // Determine turn dir + if (gameLocal.time > nextTurnUpdate) { + AI_TURN_DIR = GetTurnDir(); + nextTurnUpdate = gameLocal.time + 250; + } + } + //HUMANHEAD END + + if ( num_cinematics ) { + if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) { + PlayCinematic(); + } + RunPhysics(); + } else if ( !allowHiddenMovement && IsHidden() ) { + // hidden monsters + UpdateAIScript(); + // HUMANHEAD pdm: Vehicle support + } else if ( InVehicle() ) { + UpdateEnemyPosition(); + UpdateAIScript(); + FlyMove(); + // HUMANHEAD END + + } else { + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // HUMANHEAD NLA + physicsObj.ResetNumTouchEnt(0); + // HUMANHEAD END + switch( move.moveType ) { + case MOVETYPE_DEAD : + // dead monsters + UpdateAIScript(); + DeadMove(); + break; + + case MOVETYPE_FLY : + // flying monsters + UpdateEnemyPosition(); + UpdateAIScript(); + FlyMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_STATIC : + // static monsters + UpdateEnemyPosition(); + UpdateAIScript(); + StaticMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_ANIM : + // animation based movement + UpdateEnemyPosition(); + UpdateAIScript(); + AnimMove(); + PlayChatter(); + CheckBlink(); + break; + + case MOVETYPE_SLIDE : + // velocity based movement + UpdateEnemyPosition(); + UpdateAIScript(); + SlideMove(); + PlayChatter(); + CheckBlink(); + break; + } + // HUMANHEAD NLA + ClientImpacts(); + // HUMANHEAD END + } + + // clear pain flag so that we recieve any damage between now and the next time we run the script + AI_PAIN = false; + AI_SPECIAL_DAMAGE = 0; + AI_PUSHED = false; + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + // HUMANHEAD jrm - need to call ticker function per aaron + if (thinkFlags & TH_TICKER) { + Ticker(); + } + + if ( af_push_moveables ) { + PushWithAF(); + } + + if ( fl.hidden && allowHiddenMovement ) { + // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } + + UpdateMuzzleFlash(); + UpdateAnimation(); + UpdateParticles(); + UpdateWounds(); + Present(); + UpdateDamageEffects(); + LinkCombat(); + + CrashLand( oldOrigin, oldVelocity ); + + if(health > 0) { + idStr tmp; + if(ai_showNoAAS.GetBool() && spawnArgs.GetString("use_aas", "", tmp) && spawnArgs.GetBool("noaas_warning","1")) { + if(!aas) { + gameRenderWorld->DrawText("?", this->GetEyePosition() + idVec3(0.0f, 0.0f, 12.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + } + } + + if ( !AI_DEAD && bCanFall && !IsHidden() && !fl.isTractored ) { + if ( physicsObj.HasContacts() || physicsObj.GetLinearVelocity().LengthSqr() < 4 ) { + lastContactTime = gameLocal.time; + if ( AI_FALLING ) { + AI_FALLING = false; + bCanFall = false; + SetState( GetScriptFunction( "state_Idle" ) ); + SetWaitState( "" ); + } + } else if ( !AI_FALLING && !af.IsActive() && physicsObj.GetLinearVelocity().LengthSqr() > 0 ) { + if ( gameLocal.time - lastContactTime > fallDelay ) { + AI_FALLING = true; + SetState( GetScriptFunction( "state_Nothing" ) ); + Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Fall", 4); + Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Fall", 4); + } + } + } + + //update wallwalk + if ( bCanWallwalk && !fl.isTractored ) { + trace_t TraceInfo; + gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() + this->GetPhysics()->GetGravityNormal() * 50, GetPhysics()->GetClipMask(), this); + if( TraceInfo.fraction < 1.0f && gameLocal.GetMatterType(TraceInfo, NULL) == SURFTYPE_WALLWALK ) { + SetGravity( -TraceInfo.c.normal * DEFAULT_GRAVITY ); + AI_WALLWALK = true; + } else if ( AI_WALLWALK ) { + SetGravity( idVec3(0,0,-DEFAULT_GRAVITY) ); + AI_WALLWALK = false; + } + } else if ( AI_WALLWALK ) { + SetGravity( idVec3(0,0,-DEFAULT_GRAVITY) ); + AI_WALLWALK = false; + } + + if ( bBossBar ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + player->hud->SetStateFloat( "progress", idMath::ClampFloat( 0.0f, 1.0f, float(health) / (float)spawnHealth ) ); + player->hud->StateChanged(gameLocal.time); + player->hud->Redraw( gameLocal.realClientTime ); + } + } + + if( ai_debugBrain.GetInteger() > 0 && !IsHidden() ) { + if ( enemy.IsValid() && enemy->GetHealth() > 0 ) { + gameRenderWorld->DebugArrow( colorWhite, GetOrigin(), enemy->GetOrigin(), 10 ); + float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast(); + gameRenderWorld->DrawText( va("%i", int(dist)), this->GetEyePosition() + idVec3(0.0f, 0.0f, 60.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + if ( state ) { + if ( physicsObj.GetClipMask() == 0 ) { + gameRenderWorld->DrawText(state->Name(), GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorRed, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } else { + gameRenderWorld->DrawText(state->Name(), GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorGreen, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + } + gameRenderWorld->DrawText(torsoAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + gameRenderWorld->DrawText(legsAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + if ( !walkIK.IsActivated() ) { + gameRenderWorld->DrawText("ik disabled", GetEyePosition() + idVec3(0.0f, 0.0f, -20.0f), 0.75f, colorRed, gameLocal.GetLocalPlayer()->viewAngles.ToMat3()); + } + } +} + +void hhMonsterAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + //we don't want monsters pushed around by the player, projectiles, or splashDamange, so early out if they are. + if( !af.IsActive() ) { + if ( ent && (ent->IsType( hhProjectile::Type ) || ent->IsType( idWorldspawn::Type ) || ent->IsType( idPlayer::Type )) ) { + return; + } + } + + af.GetPhysics()->UpdateTime( gameLocal.GetTime() ); + BecomeActive(TH_THINK); + + idAI::ApplyImpulse( ent, id, point, force ); +} + +// +// hhMonsterAI::CheckValidReaction +// Helper function for using reactions. +// +bool hhMonsterAI::CheckValidReaction() { + hhReaction *reaction = targetReaction.GetReaction(); + if( !reaction || !reaction->causeEntity.IsValid() ) { + return false; + } + return true; +} + +// +// hhMonsterAI::FinishReaction +// Called once a reaction has been used/failed. If it failed, set the failed flag and exit. +// Otherwise, set any 'finish_key' we have. +// +void hhMonsterAI::FinishReaction( bool bFailed ) { + AI_USING_REACTION = false; + if( bFailed ) { + AI_REACTION_FAILED = true; + } + else { + hhReaction *reaction = targetReaction.GetReaction(); + if( reaction && reaction->desc->finish_key.Length() ) { + spawnArgs.Set( reaction->desc->finish_key.c_str(), reaction->desc->finish_val.c_str() ); + } + } + + //monster is finished with this reaction. let others use it + if ( targetReaction.entity.IsValid() ) { + targetReaction.entity->spawnArgs.Set( "react_inuse", "0" ); + } +} + +void hhMonsterAI::Killed(idEntity *inflictor, idEntity *attacker, + int damage, const idVec3 &dir, int location ) +{ + if ( AI_DEAD ) { + AI_DAMAGE = true; + return; + } + + if ( bBossBar && spawnArgs.GetBool( "remove_bar_on_death" ) ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + bBossBar = false; + player->hud->HandleNamedEvent("HideProgressBar"); + player->hud->StateChanged(gameLocal.time); + } + } + + HandleNoGore(); + + idAI::Killed(inflictor, attacker, damage, dir, location); + + fl.noPortal = 0; // CJR: Set so that killed monsters never collide with portals + + SendDamageToDDA(); // CJR DDA: Send any accumulated damage to the dda system + + // General non-item dropping (for monsters, souls, etc.) + const idKeyValue *kv = NULL; + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while ( kv ) { + + idStr drops = kv->GetValue(); + idDict args; + + idStr last5 = kv->GetKey().Right(5); + if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) { + + args.Set( "classname", drops ); + + // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up + args.Set("monsterSpawnedBy", name.c_str()); + + idVec3 origin; + idMat3 axis; + idStr jointKey = kv->GetKey() + idStr("Joint"); + idStr jointName = spawnArgs.GetString( jointKey ); + idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint"); + idStr j2jName = spawnArgs.GetString( joint2JointKey ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( args, &newEnt ); + HH_ASSERT(newEnt != NULL); + + // Spin to correct heading + if(newEnt->IsType(hhMonsterAI::Type)) { + hhMonsterAI *newAI = static_cast(newEnt); + newAI->current_yaw = current_yaw; + newAI->ideal_yaw = ideal_yaw; + } + + if(jointName.Length()) { + jointHandle_t joint = GetAnimator()->GetJointHandle( jointName ); + if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name ); + origin = renderEntity.origin; + axis = renderEntity.axis; + } + axis *= renderEntity.axis; + origin = renderEntity.origin + origin * renderEntity.axis; + newEnt->SetAxis(axis); + newEnt->SetOrigin(origin); + } + else { + + newEnt->SetAxis(viewAxis); + newEnt->SetOrigin(GetOrigin()); + } + + } + + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +void hhMonsterAI::Event_Remove( void ) { + if( InVehicle() ) { + GetVehicleInterface()->GetVehicle()->EjectPilot(); + } + + idAI::Event_Remove(); +} + +void hhMonsterAI::EnterVehicle( hhVehicle* vehicle ) { + if (!vehicle->WillAcceptPilot(this)) { + return; + } + spawnArgs.Set( "use_aas", spawnArgs.GetString( "aas_shuttle", "aasDroid" ) ); + SetAAS(); + idAI::EnterVehicle( vehicle ); + PostEventSec( &MA_SetVehicleState, 0.1f ); +} + +bool hhMonsterAI::TestMelee( void ) const { + trace_t trace; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt || !melee_range ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + idBounds meleeBounds( spawnArgs.GetVector( "melee_boundmin", "0 0 0" ), spawnArgs.GetVector( "melee_boundmax", "0 0 0" ) ); + if ( meleeBounds != bounds_zero ) { + //check custom rotated meleebound + idBox meleeBox( meleeBounds, org, renderEntity.axis ); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyEnt->GetPhysics()->GetOrigin() ); + idBox enemyBox( enemyBounds ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBox( colorYellow, meleeBox ); + } + + if ( !enemyBox.IntersectsBox( meleeBox ) ) { + return false; + } + } else { + // expand the bounds out by our melee range + bounds[0][0] = -melee_range; + bounds[0][1] = -melee_range; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = melee_range; + bounds[1][1] = melee_range; + 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 ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +bool hhMonsterAI::MoveToPosition( const idVec3 &pos, bool enemyBlocks ) { + if(AI_VEHICLE && InVehicle()) { + GetVehicleInterfaceLocal()->ThrustTowards( pos, 1.0f ); + return true; + } else { + return idAI::MoveToPosition( pos, enemyBlocks ); + } +} + +bool hhMonsterAI::TurnToward( const idVec3 &pos ) { + if (AI_VEHICLE && InVehicle()) { + GetVehicleInterfaceLocal()->OrientTowards( pos, 0.5 ); + return true; + } else { + return idAI::TurnToward( pos ); + } +} + +bool hhMonsterAI::CanSee( idEntity *ent, bool useFov ) { + trace_t tr; + idVec3 eye; + idVec3 toPos; + + if ( ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + idActor *act = static_cast(ent); + + // If this actor is in a vehicle, look at the vehicle, not the actor + if(act->InVehicle()) { + ent = act->GetVehicleInterface()->GetVehicle(); + } + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ( ( idActor * )ent )->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetOrigin(); + } + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + + eye = GetEyePosition(); + + if ( InVehicle() ) { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, GetVehicleInterface()->GetVehicle() ); // HUMANHEAD JRM + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + } else { + gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); // HUMANHEAD JRM + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } else if ( bSeeThroughPortals && aas ) { +#if 0 // jmarshall - AI portals + shootTarget = NULL; + int myArea = gameRenderWorld->PointInArea( GetOrigin() ); + int numPortals = gameRenderWorld->NumGamePortalsInArea( myArea ); + if ( numPortals > 0 ) { + int enemyArea = gameRenderWorld->PointInArea( ent->GetOrigin() ); + for ( int i=0;iGetSoundPortal( myArea, i ).areas[0] == enemyArea ) { + //find the portal and set it as this monster's shoottarget + idEntity *spawnedEnt = NULL; + for( spawnedEnt = gameLocal.spawnedEntities.Next(); spawnedEnt != NULL; spawnedEnt = spawnedEnt->spawnNode.Next() ) { + if ( !spawnedEnt->IsType( hhPortal::Type ) ) { + continue; + } + if ( gameRenderWorld->PointInArea( spawnedEnt->GetOrigin() ) == myArea) { + shootTarget = spawnedEnt; + return true; + } + } + } + } + } +#endif + } + } + + return false; +} + +idPlayer* hhMonsterAI::GetClosestPlayer(void) { + idEntity *closestEnt = NULL; + float closestDist = idMath::INFINITY; + + for(int i=0;iIsType(idPlayer::Type)); + + float l = (ent->GetOrigin()-GetOrigin()).Length(); + if(l < closestDist || !closestEnt) { + closestDist = l; + closestEnt = ent; + } + } + } + + return static_cast(closestEnt); +} + +void hhMonsterAI::Show() { + if ( spawnThinkFlags != 0 ) { + int temp = spawnThinkFlags; + spawnThinkFlags = 0; + BecomeActive( temp ); + } + idAI::Show(); + PostEventMS(&MA_EnemyOnSpawn, 10); + Event_InitialWallwalk(); +} + +bool hhMonsterAI::GetFacePosAngle( const idVec3 &pos, float &delta ) { + float diff; + float angle1; + float angle2; + idVec3 sourceOrigin; + idVec3 targetOrigin; + + sourceOrigin = GetPhysics()->GetOrigin(); + targetOrigin = pos;//target->GetPhysics()->GetOrigin(); + + angle1 = DEG2RAD( GetGravViewAxis()[0].ToYaw() ); // VIEWAXIS_TO_GETGRAVVIEWAXIS + angle2 = hhUtils::PointToAngle( targetOrigin.x - sourceOrigin.x, targetOrigin.y - sourceOrigin.y ); + if(angle2 > angle1) { + diff = angle2 - angle1; + + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return false; + } + else { + delta = diff; + return true; + } + } + else { + diff = angle1 - angle2; + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return true; + } + else { + delta = diff; + return false; + } + } +} + +void hhMonsterAI::Distracted( idActor *newEnemy ) { + SetEnemy( newEnemy ); +} + +void hhMonsterAI::SetEnemy( idActor *newEnemy ) { + idAI::SetEnemy( newEnemy ); +} + +idVec3 hhMonsterAI::GetTouchPos(idEntity *ent, const hhReactionDesc *desc ) { + assert(ent != NULL); + assert(desc != NULL); + + idVec3 pos = ent->GetOrigin(); + + idVec3 offset = desc->touchOffsets.GetVector("all", "0 0 0"); + if ( offset == vec3_zero ) { + // Each monster def type has its own offset etc. + // touchoffset_monster_hunter + offset = desc->touchOffsets.GetVector(GetEntityDefName(), "0 0 0"); + } + idStr touchDirType = desc->touchDir; + + // When touching this ent, move to it as if it were 'cover' + if( touchDirType == idStr("cover") && enemy.IsValid() ) { + idVec3 goalPos = ent->GetOrigin() + offset.x * (ent->GetOrigin() - GetEnemy()->GetOrigin()).ToNormal(); + goalPos.z += offset.z; + return goalPos; + } + // Move directly toward this ent from our pos + else if(touchDirType == idStr("direct")) { + idVec3 dir = GetOrigin() - pos; + dir.Normalize(); + offset *= dir.ToMat3(); + return pos + offset; + } + else if(touchDirType == idStr("object")) { + ent->GetFloorPos( 64.f, pos ); + idVec3 dir = GetOrigin() - pos; + dir.Normalize(); + return pos + (dir * offset.x); + } + else { + offset *= ent->GetAxis(); + return pos + offset; + } + + +} + +bool hhMonsterAI::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + origin = modelOffset; + if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { + idMat3 masterAxis; + idVec3 masterOrigin; + GetMasterPosition( masterOrigin, masterAxis ); + axis = physicsObj.localAxis * masterAxis; + origin = masterOrigin + physicsObj.GetLocalOrigin() * masterAxis; + } else if ( ( InVehicle() || bBindOrient ) && GetBindMaster() ) { + axis = GetBindMaster()->GetAxis(); + } else { + axis = viewAxis; + } + return true; +} + +void hhMonsterAI::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + if ( bBindAxis && GetBindMaster() ) { + renderEntity.axis = GetBindMaster() ->GetAxis(); + } else { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + } + if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { + if ( head.IsValid() && head->GetPhysics() ) { + head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); + } + renderEntity.origin = origin; + } else { + if ( GetBindMaster() && head.IsValid() && head->GetPhysics() ) { + head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); + } + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +void hhMonsterAI::UpdateFromPhysics( bool moveBack ) { + // set master delta angles for actors + if ( GetBindMaster() ) { + if( !InVehicle() ) { + idAngles delta = GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= physicsObj.GetMasterDeltaYaw(); + } else { + delta.yaw += physicsObj.GetMasterDeltaYaw(); + } + + SetDeltaViewAngles( delta ); + } else { + SetAxis( GetBindMaster()->GetAxis() ); + } + } + + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } + + UpdateVisuals(); +} + +void hhMonsterAI::CreateHealthTriggers() { + const char keyPrefix[] = "health_percent_trigger_"; + int keyPrefixLen = strlen(keyPrefix); + + const idKeyValue *kv = spawnArgs.MatchPrefix(keyPrefix, NULL); + healthTriggers.Clear(); + while(kv) { + hhMonsterHealthTrigger t; + idStr k = kv->GetKey(); + idStr perct = k.Right(k.Length() - strlen(keyPrefix)); + float p = float(atoi(perct.c_str())) * 0.01f; + t.healthThresh = int(float(health) * p); + t.triggerEnt = gameLocal.FindEntity(kv->GetValue().c_str()); + if(!t.triggerEnt.GetEntity()) { + gameLocal.Warning("%s specified %s key with entity \"%s\", but that entity does not exist!", name.c_str(), kv->GetKey().c_str(), kv->GetValue().c_str()); + } + healthTriggers.Append(t); + kv = spawnArgs.MatchPrefix(keyPrefix, kv); + } +} + +void hhMonsterAI::UpdateHealthTriggers(int oldHealth, int currHealth) { + + // Trigger them + for(int i=0;iProcessEvent(&EV_Activate, this); + healthTriggers[i].triggerCount++; + } + } + } +} + +// +// Damage() +// +void hhMonsterAI::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Return if we don't take damage + if ( !fl.takedamage ) { + return; + } + + if ( spawnArgs.GetBool( "noPlayerDamage", "0" ) ) { + if ( attacker && attacker->IsType( idPlayer::Type ) ) { + return; + } + } + + int oldHealth = health; + idAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); + UpdateHealthTriggers(oldHealth, health); + + if ( bCustomBlood && inflictor ) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_custom_blood", inflictor->GetOrigin(), mat3_identity, &fxInfo ); + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( AI_DEAD && damageDef ) { + if ( damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) { + SetSkinByName( spawnArgs.GetString( "skin_freeze" ) ); + + if( af.IsLoaded() ) { + af.GetPhysics()->SetSelfCollision(false); + af.GetPhysics()->SetContactFrictionScale(0); + af.GetPhysics()->SetTimeScaleRamp(0,2); + + for(int i=0; i < af.GetPhysics()->GetNumConstraints(); i++) { + idAFConstraint* constraint = af.GetPhysics()->GetConstraint(i); + switch( constraint->GetType() ) { + case CONSTRAINT_BALLANDSOCKETJOINT: + static_cast(constraint)->SetFriction(200.0f); + static_cast(constraint)->SetNoLimit(); + break; + case CONSTRAINT_UNIVERSALJOINT: + static_cast(constraint)->SetFriction(200.0f); + static_cast(constraint)->SetNoLimit(); + break; + case CONSTRAINT_HINGE: + static_cast(constraint)->SetFriction(200.0f); + static_cast(constraint)->SetNoLimit(); + break; + } + } + } + + spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_ice" )); + spawnArgs.Set("produces_splats", "0"); + if( modelDefHandle > 0 ) + gameRenderWorld->RemoveDecals( modelDefHandle ); + //SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 5000, 12000); + SetShaderParm(SHADERPARM_TIME_OF_DEATH, MS2SEC(gameLocal.time+100000)); + CancelEvents( &EV_Dispose ); + PostEventSec( &EV_Dispose, 5 ); + } + if ( damageDef->GetBool( "burn" ) && !spawnArgs.GetBool( "no_burn" ) ) { + SetSkinByName( spawnArgs.GetString( "skin_burn" ) ); + spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_burn" )); + if( modelDefHandle > 0 ) + gameRenderWorld->RemoveDecals( modelDefHandle ); + CancelEvents( &EV_Dispose ); + PostEventSec( &EV_Dispose, 0 ); + } + if ( damageDef->GetBool( "acid" ) && spawnArgs.GetBool( "acidburn" ) ) { + SetSkinByName( spawnArgs.GetString( "skin_acidburn" ) ); + SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 2500, 8000); // starttime, duration + PostEventSec( &EV_StartSound, 1.5f, "snd_acid", SND_CHANNEL_ANY, 1 ); + spawnArgs.Set("fx_deatheffect", spawnArgs.GetString( "fx_acid" )); + spawnArgs.Set("mtr_splat1", spawnArgs.GetString( "mtr_acidsplat" )); + spawnArgs.Delete("mtr_splat2"); + spawnArgs.Delete("mtr_splat3"); + spawnArgs.Delete("mtr_splat4"); + spawnArgs.Set("keepDecals", "1"); + + CancelEvents( &EV_Dispose ); + PostEventSec( &EV_Dispose, 1.5 ); + } + } +} + +bool hhMonsterAI::NearEnoughTouchPos( idEntity* ent, const idVec3& targetPos, idBounds& bounds ) { + bounds.TranslateSelf( targetPos ); + + idBounds bnds( idVec3(-16.f, -16.f, -8.f), idVec3(16.f, 16.f, 64.f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); +//uncomment these to show debug lines +// gameRenderWorld->DebugBounds( colorOrange, bounds ); +// gameRenderWorld->DebugArrow( colorRed, targetPos, targetPos + idVec3(0, 0, 10), 5 ); +// gameRenderWorld->DebugBounds( colorRed, bnds ); + if( bnds.IntersectsBounds( bounds ) ) { + return true; + } + return false; +} + +bool hhMonsterAI::GetTouchPosBound( const hhReactionDesc *desc, idBounds & bnds ) { + idStr minName, maxName; + idVec3 min, max; + + minName = va("min_%s", GetEntityDefName()); + maxName = va("max_%s", GetEntityDefName()); + + if( !desc->touchOffsets.GetVector(minName.c_str(), "0 0 0", min) || !desc->touchOffsets.GetVector(maxName.c_str(), "0 0 0", max) ) { + return false; + } + min -= idVec3( 16.f, 16.f, 8.f ); + max += idVec3( 16.f, 16.f, 64.f ); + + bnds.Clear(); + bnds[ 0 ] = min; + bnds[ 1 ] = max; + return true; +} + +int hhMonsterAI::EvaluateReaction( const hhReaction *react ) { +// Volume/Distance + // If a volume in specified, then use that as our spatial check + float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr(); + + if( react->desc->listenerVolumes.Num() > 0 ) { + int count = 0; + int i; + for( i = 0; i < react->desc->listenerVolumes.Num(); i++ ) { + assert( react->desc->listenerVolumes[ i ]->GetPhysics() != NULL ); + assert( GetPhysics() != NULL ); + if( react->desc->listenerVolumes[ i ]->GetPhysics()->GetAbsBounds().IntersectsBounds( GetPhysics()->GetAbsBounds() ) ) { + //MDC-TODO: Draw bounds debugging info... + count++; //one is good enough to continue - no need to check the rest + break; + } + } + // If we are in ZERO of the listener volumes, then we can bail because this reaction doesn't apply to us + if( count == 0 ) { + return 0; + } + } + if ( react->desc->effectVolumes.Num() > 0 && GetEnemy() ) { + int count = 0; + for( int i = 0; i < react->desc->effectVolumes.Num(); i++ ) { + assert( react->desc->effectVolumes[ i ]->GetPhysics() != NULL ); + assert( GetPhysics() != NULL ); + if( react->desc->effectVolumes[ i ]->GetPhysics()->GetAbsBounds().IntersectsBounds( GetEnemy()->GetPhysics()->GetAbsBounds() ) ) { + count++; //one is good enough to return false - no need to check the rest + break; + } + } + // If enemy is in ZERO of the effect volumes, then we can bail because this reaction doesn't apply to us + if( count == 0 ) { + return 0; + } + } + if( react->desc->listenerRadius > 0.f || react->desc->listenerMinRadius > 0.f ) { + // TOO FAR + if( react->desc->listenerRadius > 0.f && distSq > react->desc->listenerRadius * react->desc->listenerRadius ) { + return 0; + } + // TOO CLOSE + if( react->desc->listenerMinRadius > 0.f && distSq < react->desc->listenerMinRadius * react->desc->listenerMinRadius ) { + return 0; + } + } +// Path-finding + if( react->desc->CauseRequiresPathfinding() ) { + if( !HasPathTo( GetTouchPos(react->causeEntity.GetEntity(), react->desc ) ) ) { + //MDC-TODO: draw path debugging + return 0; + } + else { + //MDC-TODO: draw path debugging + } + } +// Can-See + if( react->desc->flags & hhReactionDesc::flagReq_CanSee ) { + if( react->causeEntity.IsValid() ) { + if( !CanSee(react->causeEntity.GetEntity(), TRUE) ) { + return 0; + } + } + } + + return 100; +} + +int hhMonsterAI::ReactionTo( const idEntity *ent ) { + if ( bNoCombat ) { + return ATTACK_IGNORE; + } + const idActor *actor = static_cast( ent ); + if( actor && actor->IsType(hhDeathProxy::Type) ) { + return ATTACK_IGNORE; + } + + //only attack spiritwalking players if they hurt me + if ( ent->IsType( hhPlayer::Type ) ) { + const hhPlayer *player = static_cast( ent ); + if ( nextSpiritProxyCheck == 0 && player && player->IsSpiritWalking() ) { + nextSpiritProxyCheck = gameLocal.time + SEC2MS(2); + return ATTACK_ON_DAMAGE; + } + } + + if ( ent->IsType( hhSpiritProxy::Type ) ) { + if ( gameLocal.time > nextSpiritProxyCheck ) { + nextSpiritProxyCheck = 0; + //attack spiritproxy on sight if we have no enemy or if its closer than our current enemy + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + float distToEnemy = (enemy->GetOrigin() - GetOrigin()).LengthSqr(); + float distToProxy = (ent->GetOrigin() - GetOrigin()).LengthSqr(); + if ( distToProxy < distToEnemy ) { + return ATTACK_ON_SIGHT; + } else { + //HUMANHEAD jsh PCF 4/29/06 fixed logic error with spiritproxy checking + return ATTACK_IGNORE; + } + } else { + return ATTACK_ON_SIGHT; + } + } else { + return ATTACK_IGNORE; + } + } + + if ( ent->IsType( hhMonsterAI::Type ) ) { + const hhMonsterAI *entAI = static_cast( ent ); + if ( entAI && entAI->bNeverTarget ) { + return ATTACK_IGNORE; + } + } + + return idAI::ReactionTo( ent ); +} + +bool hhMonsterAI::UpdateAnimationControllers( void ) { + idVec3 local; + idVec3 focusPos; + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idAngles diff; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idAFAttachment *headEnt = head.GetEntity(); + idVec3 eyepos; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + + if ( AI_DEAD ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 ); + } + + if ( focusJoint != INVALID_JOINT ) { + if ( headEnt ) { + headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } else { + // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that? + GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis ); + } + eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z; + } else { + eyepos = GetEyePosition(); + } + + if ( headEnt ) { + CopyJointsFromBodyToHead(); + } + + // 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(); + + idEntity *focusEnt = focusEntity.GetEntity(); + //HUMANHEAD jsh allow eyefocus independent from allowJointMod + if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) || GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1) ) { + focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f; + } else if ( focusEnt == NULL ) { + // keep looking at last position until focusTime is up + focusPos = currentFocusPos; + } else if ( focusEnt == enemy.GetEntity() ) { + focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal(); + } else if ( focusEnt->IsType( idActor::Type ) ) { + focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + focusPos = focusEnt->GetPhysics()->GetOrigin(); + } + + currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate; + + // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles + dir = focusPos - orientationJointPos; + newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw ); + newLookAng.roll = 0.0f; + newLookAng.pitch = 0.0f; + + newLookAng += lookOffset; + +#if 0 + gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec ); + gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec ); +#endif + +//JRMMERGE_GRAVAXIS: This changed to much to merge, see if you can get your monsters on planets changes back in here. I'll leave both versions +#if OLD_CODE + GetGravViewAxis().ProjectVector( dir, localDir ); // HUMANHEAD JRM: VIEWAXIS_TO_GETGRAVVIEWAXIS + lookAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + lookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + lookAng.roll = 0.0f; +#else + // determine pitch from joint position + dir = focusPos - eyepos; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 ); + } + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch; + newLookAng.roll = 0.0f; +#endif + + diff = newLookAng - lookAng; + + if ( eyeAng != diff ) { + eyeAng = diff; + eyeAng.Clamp( eyeMin, eyeMax ); + idAngles angDelta = diff - eyeAng; + if ( !angDelta.Compare( ang_zero, 0.1f ) ) { + alignHeadTime = gameLocal.time; + } else { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + } + } + + if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) { + alignHeadTime = gameLocal.time; + } + + if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) { + alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime; + destLookAng = newLookAng; + destLookAng.Clamp( lookMin, lookMax ); + } + + diff = destLookAng - lookAng; + if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) { + if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) { + diff.pitch = 360.0f - diff.pitch; + } + } + if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) { + if ( diff.yaw > 180.0f ) { + diff.yaw -= 360.0f; + } else if ( diff.yaw <= -180.0f ) { + diff.yaw += 360.0f; + } + } + lookAng = lookAng + diff * headFocusRate; + lookAng.Normalize180(); + + jointAng.roll = 0.0f; + if ( allowJointMod ) { + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = 0; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + + idMat3 pitchRot; + hhMath::BuildRotationMatrix( DEG2RAD(-lookAng.pitch * lookJointAngles[ i ].pitch), 0, pitchRot ); + animator.GetJointLocalTransform( lookJoints[ i ], gameLocal.time, pos, mat ); + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_LOCAL_OVERRIDE, pitchRot * mat ); + } + } + + if ( move.moveType == MOVETYPE_FLY ) { + // lean into turns + AdjustFlyingAngles(); + } + + if ( headEnt ) { + idAnimator *headAnimator = headEnt->GetAnimator(); + + // HUMANHEAD pdm: Added support for look joints in head entities + if ( allowJointMod ) { + for( i = 0; i < headLookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw; + headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + // HUMANHEAD END + + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos -= headEnt->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose ); + headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose ); + + //if ( ai_debugMove.GetBool() ) { + // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec ); + //} + } else { + headEnt->BecomeActive( TH_ANIMATE ); + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus ) { + idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); + axis = eyeAxis * orientationJointAxis; + left = axis[ 1 ] * eyeHorizontalOffset; + eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin(); + animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left ); + animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + + //HUMANHEAD pdm jawflap + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + //END HUMANHEAD + + return true; +} + +//overridden to allow attack nodes farther away than our current enemydistance +void hhMonsterAI::Event_GetCombatNode( void ) { + int i; + float dist; + idEntity *targetEnt; + idCombatNode *node; + float bestDist; + idCombatNode *bestNode; + idActor *enemyEnt = enemy.GetEntity(); + + if ( !targets.Num() ) { + // no combat nodes + idThread::ReturnEntity( NULL ); + return; + } + + if ( !enemyEnt || !EnemyPositionValid() ) { + // don't return a combat node if we don't have an enemy or + // if we can see he's not in the last place we saw him + idThread::ReturnEntity( NULL ); + return; + } + + // find the closest attack node that can see our enemy and is closer than our enemy + bestNode = NULL; + const idVec3 &myPos = physicsObj.GetOrigin(); + bestDist = 9999999.0f ; + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { + continue; + } + + node = static_cast( targetEnt ); + if ( !node->IsDisabled() && node->EntityInView( enemyEnt, lastVisibleEnemyPos ) ) { + idVec3 org = node->GetPhysics()->GetOrigin(); + dist = ( myPos - org ).LengthSqr(); + if ( dist < bestDist ) { + bestNode = node; + bestDist = dist; + } + } + } + + idThread::ReturnEntity( bestNode ); +} + +void hhMonsterAI::FlyTurn( void ) { //overridden for vehicle code changes + if ( move.moveCommand == MOVE_FACE_ENEMY ) { + TurnToward( lastVisibleEnemyPos ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + if ( InVehicle() ) { + if ( move.goalEntity.IsValid() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else { + TurnToward( GetVehicleInterface()->GetVehicle()->GetPhysics()->GetLinearVelocity().ToYaw() ); + } + } else { + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + } + } + Turn(); +} + +//overridden to allow custom hearingRange +void hhMonsterAI::UpdateEnemyPosition( void ) { + idActor *enemyEnt = enemy.GetEntity(); + int enemyAreaNum; + int areaNum; + aasPath_t path; + predictedPath_t predictedPath; + idVec3 enemyPos; + bool onGround; + + if ( !enemyEnt ) { + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + if ( move.moveType == MOVETYPE_FLY ) { + enemyPos = enemyEnt->GetPhysics()->GetOrigin(); + onGround = true; + } else { + onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos ); + if ( enemyEnt->OnLadder() ) { + onGround = false; + } + } + + if ( onGround ) { + // when we don't have an AAS, we can't tell if an enemy is reachable or not, + // so just assume that he is. + if ( !aas ) { + enemyAreaNum = 0; + lastReachableEnemyPos = enemyPos; + } else { + enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f ); + if ( enemyAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) { + lastReachableEnemyPos = enemyPos; + } + } + } + } + + AI_ENEMY_IN_FOV = false; + AI_ENEMY_VISIBLE = false; + + if ( CanSee( enemyEnt, false ) ) { + AI_ENEMY_VISIBLE = true; + if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) { + AI_ENEMY_IN_FOV = true; + } + + SetEnemyPosition(); + } else { + // check if we heard any sounds in the last frame + if ( enemyEnt == gameLocal.GetAlertEntity() ) { + float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr(); + //allow the sound's own radius to override hearingRange, if it is set + if ( gameLocal.lastAIAlertRadius ) { + if ( dist < Square( gameLocal.lastAIAlertRadius ) ) { + SetEnemyPosition(); + } + } else if ( dist < Square( hearingRange ) ) { + SetEnemyPosition(); + } + } + } + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec ); + } +} + +void hhMonsterAI::BecameBound(hhBindController *b) { + SetState( GetScriptFunction( "state_Nothing" ) ); +} + +void hhMonsterAI::BecameUnbound(hhBindController *b) { + if ( health > 0 ) { + SetState( GetScriptFunction( "state_Idle" ) ); + } +} + +void hhMonsterAI::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + const trace_t& trace = physicsObj.GetGroundTrace(); + if ( af.IsActive() || (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { + return; + } + + //aob - only check when we land on the ground + //If we get here we can assume we currently have ground contacts + if( physicsObj.HadGroundContacts() ) { + return; + } + + // if the monster wasn't going down + if ( ( oldVelocity * -physicsObj.GetGravityNormal() ) >= 0.0f ) { + return; + } + + idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); + float delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * physicsObj.GetGravityNormal(); + + if ( delta < spawnArgs.GetFloat( "fatal_fall_velocity", "900" ) ) { + return; // Early out + } + if( trace.fraction == 1.0f ) { + return; + } + Damage( NULL, NULL, oldVelocity.ToNormal(), "damage_monsterfall", 1, INVALID_JOINT ); +} + +hhEntityFx* hhMonsterAI::SpawnFxLocal( const char *fxName, const idVec3 &origin, const idMat3& axis, const hhFxInfo* const fxInfo, bool forceClient ) { + //overridden to use GetJointWorldTransform to set location if binding to a bone + idDict fxArgs; + hhEntityFx * fx = NULL; + + if ( g_skipFX.GetBool() ) { + return NULL; + } + + if( !fxName || !fxName[0] ) { + return NULL; + } + + // Spawn an fx + fxArgs.Set( "fx", fxName ); + fxArgs.SetBool( "start", fxInfo ? fxInfo->StartIsSet() : true ); + fxArgs.SetVector( "origin", origin ); + fxArgs.SetMatrix( "rotation", axis ); + //HUMANHEAD: aob + if( fxInfo ) { + fxArgs.SetBool( "removeWhenDone", fxInfo->RemoveWhenDone() ); + fxArgs.SetBool( "onlyVisibleInSpirit", fxInfo->OnlyVisibleInSpirit() ); // CJR + fxArgs.SetBool( "onlyInvisibleInSpirit", fxInfo->OnlyInvisibleInSpirit() ); // tmj + fxArgs.SetBool( "toggle", fxInfo->Toggle() ); + } + //HUMANHEAD END + + //HUMANHEAD rww - use forceClient + if (forceClient) { + //this can happen on the "server" in the case of listen servers as well + fx = (hhEntityFx *)gameLocal.SpawnClientObject( "func_fx", &fxArgs ); + } + else { + assert(!gameLocal.isClient); + fx = (hhEntityFx *)gameLocal.SpawnObject( "func_fx", &fxArgs ); + } + if( fxInfo ) { + fx->SetFxInfo( *fxInfo ); + } + + if( fxInfo && fxInfo->EntityIsSet() ) { + fx->fl.noRemoveWhenUnbound = fxInfo->NoRemoveWhenUnbound(); + if( fxInfo->BindBoneIsSet() ) { + idVec3 bonePos; + idMat3 boneAxis; + GetJointWorldTransform( fxInfo->GetBindBone(), bonePos, boneAxis ); + fx->SetOrigin( bonePos ); + fx->BindToJoint( fxInfo->GetEntity(), fxInfo->GetBindBone(), true ); + } else if( fx && fx->Joint() && *fx->Joint() ) { + fx->MoveToJoint( fxInfo->GetEntity(), fx->Joint() ); + fx->BindToJoint( fxInfo->GetEntity(), fx->Joint(), true ); + } else { + fx->Bind( fxInfo->GetEntity(), true ); + } + } + + fx->Show(); + + return fx; +} + +bool hhMonsterAI::AttackMelee( const char *meleeDefName ) { + const idDict *meleeDef; + idActor *enemyEnt = enemy.GetEntity(); + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName ); + } + + if ( !enemyEnt ) { + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // make sure the trace can actually hit the enemy + if ( !TestMeleeDef(meleeDefName) ) { + // missed + p = meleeDef->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // 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; + + if ( spawnArgs.GetBool( "smart_knockback", "0" ) ) { + //do some traces to determine which dir to kick enemy + idAngles ang = viewAxis.ToAngles(); + idVec3 forward, right, up; + ang.ToVectors( &forward, &right, &up ); + trace_t trace; + gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + -forward * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); + if ( trace.fraction != 1.0f ) { + //there's room to kick the player back + globalKickDir = -forward; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); + } + } else { + gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + right * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); + if ( trace.fraction != 1.0f ) { + globalKickDir = right; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); + } + } else { + gameLocal.clip.TraceBounds( trace, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + right * 400, enemyEnt->GetPhysics()->GetBounds(), MASK_SOLID, this ); + if ( trace.fraction != 1.0f ) { + globalKickDir = -right; + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, enemyEnt->GetOrigin(), enemyEnt->GetOrigin() + globalKickDir * 500, 10, 10000 ); + } + } + } + } + } + + enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + + //swap skin if we have successfully hit + idStr hitSkin = spawnArgs.GetString( "skin_melee_hit", "" ); + if ( hitSkin.Length() ) { + SetSkinByName( hitSkin.c_str() ); + } + + lastAttackTime = gameLocal.time; + + return true; +} + +bool hhMonsterAI::TestMeleeDef( const char *meleeDefName ) const { + //tests using "melee_boundmin" and "melee_boundmax" in the damage def + trace_t trace; + idActor *enemyEnt = enemy.GetEntity(); + const idDict *meleeDef; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s'", meleeDefName ); + } + + if ( !enemyEnt || !melee_range ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + idBounds meleeBounds( meleeDef->GetVector( "melee_boundmin" ), meleeDef->GetVector( "melee_boundmax" ) ); + + if ( meleeBounds != bounds_zero ) { + //check custom rotated meleebound + idBox meleeBox( meleeBounds, org, renderEntity.axis ); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyEnt->GetPhysics()->GetOrigin() ); + idBox enemyBox( enemyBounds ); + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBox( colorYellow, meleeBox, 5000 ); + } + + if ( !enemyBox.IntersectsBox( meleeBox ) ) { + return false; + } + } else { + // expand the bounds out by our melee range + bounds[0][0] = -melee_range; + bounds[0][1] = -melee_range; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = melee_range; + bounds[1][1] = melee_range; + 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 ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +void hhMonsterAI::PrintDebug() { + if ( enemy.IsValid() ) { + int dist = int(( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast()); + gameLocal.Printf( " Enemy: %s\n", enemy->GetName() ); + gameLocal.Printf( " Distance to Enemy: %d\n", dist ); + gameLocal.Printf( " Enemy Area: %i\n", PointReachableAreaNum(enemy->GetOrigin()) ); + gameLocal.Printf( " Enemy Reachable: %s\n", AI_ENEMY_REACHABLE ? "yes" : "no" ); + gameLocal.Printf( " Enemy Visible: %s\n", AI_ENEMY_VISIBLE ? "yes" : "no" ); + } else { + gameLocal.Printf( " Enemy: None\n" ); + } + gameLocal.Printf( " Current Area: %i\n", PointReachableAreaNum(GetOrigin()) ); + if ( state ) { + gameLocal.Printf( " State: %s\n", state->Name() ); + } + gameLocal.Printf( " Health: %i/%i\n", GetHealth(), spawnArgs.GetInt( "health" ) ); + gameLocal.Printf( " Torso State: %s\n", torsoAnim.state.c_str() ); + gameLocal.Printf( " Legs State: %s\n", legsAnim.state.c_str() ); + gameLocal.Printf( " IK: %s\n", walkIK.IsActivated() ? "enable" : "disabled" ); + gameLocal.Printf( " Turnrate: %f\n", turnRate ); +} + +bool hhMonsterAI::NewWanderDir( const idVec3 &dest ) { + if ( spawnArgs.GetInt( "wander_radius" ) ) { + //pick a new dest based on radius from starting origin + idVec3 offset = hhUtils::RandomVector() * float(spawnArgs.GetInt( "wander_radius" )); + offset.z = 0.0f; + const idVec3 newDest = spawnOrigin + offset; + return idAI::NewWanderDir( newDest ); + } else { + return idAI::NewWanderDir( dest ); + } +} + +/* +===================== +hhMonsterAI::Save +===================== +*/ +void hhMonsterAI::Save( idSaveGame *savefile ) const { + savefile->WriteInt( targetReaction.reactionIndex ); + targetReaction.entity.Save( savefile); + shootTarget.Save( savefile ); + currPassageway.Save( savefile ); + + savefile->WriteBool( bCanFall ); + + int i, num = healthTriggers.Num(); + savefile->WriteInt( num ); + for (i = 0; i < num; i++) { + savefile->WriteInt( healthTriggers[i].healthThresh ); + healthTriggers[i].triggerEnt.Save( savefile ); + savefile->WriteInt( healthTriggers[i].triggerCount ); + } + savefile->WriteInt( hearingRange ); + savefile->WriteInt( lastContactTime ); + savefile->WriteBool( bSeeThroughPortals ); + savefile->WriteBool( bBossBar ); + savefile->WriteInt( nextSpeechTime ); + savefile->WriteAngles( lookOffset ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteBool( bBindOrient ); + savefile->WriteInt( numDDADamageSamples ); + savefile->WriteFloat( totalDDADamage ); + savefile->WriteInt( spawnThinkFlags ); + savefile->WriteBool( bCanWallwalk ); + savefile->WriteBool( bOverrideKilledByGravityZones ); + savefile->WriteInt( fallDelay ); + savefile->WriteBool( soundOnModel ); + savefile->WriteBool( bBindAxis ); + savefile->WriteBool( bCustomBlood ); + savefile->WriteBool( bNoCombat ); + savefile->WriteBool( bNeverTarget ); + savefile->WriteInt( nextSpiritProxyCheck ); +}; + +/* +===================== +hhMonsterAI::Restore +===================== +*/ +void hhMonsterAI::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( targetReaction.reactionIndex ); + targetReaction.entity.Restore( savefile); + + shootTarget.Restore( savefile ); + currPassageway.Restore( savefile ); + + savefile->ReadBool( bCanFall ); + + int i, num; + savefile->ReadInt( num ); + healthTriggers.SetNum( num ); + for (i = 0; i < num; i++) { + savefile->ReadInt( healthTriggers[i].healthThresh ); + healthTriggers[i].triggerEnt.Restore( savefile ); + savefile->ReadInt( healthTriggers[i].triggerCount ); + } + savefile->ReadInt( hearingRange ); + savefile->ReadInt( lastContactTime ); + savefile->ReadBool( bSeeThroughPortals ); + savefile->ReadBool( bBossBar ); + savefile->ReadInt( nextSpeechTime ); + savefile->ReadAngles( lookOffset ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadBool( bBindOrient ); + savefile->ReadInt( numDDADamageSamples ); + savefile->ReadFloat( totalDDADamage ); + savefile->ReadInt( spawnThinkFlags ); + savefile->ReadBool( bCanWallwalk ); + savefile->ReadBool( bOverrideKilledByGravityZones ); + savefile->ReadInt( fallDelay ); + savefile->ReadBool( soundOnModel ); + savefile->ReadBool( bBindAxis ); + savefile->ReadBool( bCustomBlood ); + savefile->ReadBool( bNoCombat ); + savefile->ReadBool( bNeverTarget ); + savefile->ReadInt( nextSpiritProxyCheck ); + + allSimpleMonsters.AddUnique(this); + + nextTurnUpdate = 0; + + if ( AI_VEHICLE ) { + if ( GetVehicleInterface() ) { + ResetClipModel(); + hhVehicle *vehicle = GetVehicleInterface()->GetVehicle(); + spawnArgs.Set( "use_aas", spawnArgs.GetString( "aas_shuttle", "aasDroid" ) ); + SetAAS(); + SetState( GetScriptFunction( "state_VehicleCombat" ) ); + } + } +}; + +/* +===================== +hhMonsterAI::GetTurnDir +Returns -1 if left, 0 if facing ideal, and 1 if turning right +===================== +*/ +int hhMonsterAI::GetTurnDir( void ) { + float diff; + + if ( !turnRate ) { + return 0; + } + + diff = idMath::AngleNormalize180( current_yaw - ideal_yaw ); + if ( idMath::Fabs( diff ) < 0.01f ) { + // force it to be exact + current_yaw = ideal_yaw; + return 0; + } + + return (diff < 0 ? -1 : 1); +} + + + +//============================================================================= +// +// hhMonsterAI::SendDamageToDDA() +// +// Informs the player of the amount of damage inflicted upon it until this +// creature was killed. +//============================================================================= + +void hhMonsterAI::SendDamageToDDA() { + if ( gameLocal.isMultiplayer ) { + return; + } + + int ddaIndex = spawnArgs.GetInt( "ddaIndex", "0" ); + + if ( gameLocal.GetDDA() && ddaIndex >= 0 ) { // Only include certain creatures in the DDA calculation (exclude such things as crawlers and NPCs) + gameLocal.GetDDA()->DDA_AddDamage( ddaIndex, totalDDADamage ); + gameLocal.GetDDA()->DDA_AddSurvivalHealth( ddaIndex, gameLocal.GetLocalPlayer()->GetHealth() ); + } + + totalDDADamage = 0; +} + +void hhMonsterAI::FlyMove( void ) { + idVec3 goalPos; + idVec3 oldorigin; + idVec3 newDest; + + AI_BLOCKED = false; + if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( goalPos ) ) { + CheckObstacleAvoidance( goalPos, newDest ); + goalPos = newDest; + } + + if ( !AI_FLY_NO_SEEK && move.speed ) { + FlySeekGoal( vel, goalPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) { + AdjustFlyHeight( vel, goalPos ); + } + + AdjustFlySpeed( vel ); + + 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( disableGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee() ) { + DirectDamage( attack, enemy.GetEntity() ); + } else { + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + AI_BLOCKED = true; + } + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( ai_debugMove.GetBool() ) { + 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, goalPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +void hhMonsterAI::Activate( idEntity *activator ) { + if ( spawnThinkFlags != 0 ) { + int temp = spawnThinkFlags; + spawnThinkFlags = 0; + BecomeActive( temp ); + } + idAI::Activate( activator ); +} + +void hhMonsterAI::Hide() { + if ( spawnThinkFlags == 0 && ai_hideSkipThink.GetBool() ) { + if ( ( spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "portal" ) ) ) { + spawnThinkFlags = thinkFlags; + thinkFlags = 0; + } + } + idAI::Hide(); +} + +void hhMonsterAI::HideNoDormant() { + idAI::Hide(); +} + +void hhMonsterAI::BecomeActive( int flags ) { + if ( spawnThinkFlags != 0 ) { + spawnThinkFlags |= flags; + return; + } + idAI::BecomeActive( flags ); +} + +void hhMonsterAI::HandleNoGore(void) { + if (GERMAN_VERSION || g_nogore.GetBool()) { + fl.takedamage = false; + fl.canBeTractored = false; + GetPhysics()->SetContents(0); + float time = spawnArgs.GetFloat("nogore_dispose_time", "1"); + if (time > 0.0f) { + //PostEventSec(&EV_Dispose, time); + } + } +} + +bool hhMonsterAI::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundOnModel ) { + idVec3 boneOrigin; + idMat3 boneAxis; + jointHandle_t bone = animator.GetJointHandle( spawnArgs.GetString( "sound_joint", "b1" ) ); + if ( bone != INVALID_JOINT && animator.GetJointTransform( bone, gameLocal.time, boneOrigin, boneAxis ) ) { + origin = boneOrigin; + axis = boneAxis * viewAxis; + return true; + } + } + return idAI::GetPhysicsToSoundTransform( origin, axis ); +} + +void hhMonsterAI::AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ) { + if ( bCustomBlood ) { + return; + } + + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, damageDefIndex ) ); + if ( def == NULL ) { + return; + } + + surfTypes_t matterType = gameLocal.GetMatterType( this, collisionMaterial, "idEntity::AddLocalMatterWound" ); + + GetWoundManager()->AddWounds( def, matterType, jointNum, localOrigin, localNormal, localDir ); + + if ( head.IsValid() ) { + head->AddLocalMatterWound( jointNum, localOrigin, localNormal, localDir, damageDefIndex, collisionMaterial ); + } + //TODO: Grab head projection code from below and put into ApplyImpactMark() +} diff --git a/neo/prey/Prey/game_monster_ai.h b/neo/prey/Prey/game_monster_ai.h new file mode 100644 index 00000000..1dcbe426 --- /dev/null +++ b/neo/prey/Prey/game_monster_ai.h @@ -0,0 +1,262 @@ + +#ifndef __PREY_MONSTER_AI_H__ +#define __PREY_MONSTER_AI_H__ + +extern const idEventDef MA_AttackMissileEx; +extern const idEventDef MA_FindReaction; +extern const idEventDef MA_InitialWallwalk; +extern const idEventDef MA_EnemyOnSpawn; +extern const idEventDef MA_SetVehicleState; +extern const idEventDef MA_OnProjectileLaunch; +extern const idEventDef MA_OnProjectileHit; +extern const idEventDef MA_FallNow; +extern const idEventDef MA_EnemyIsSpirit; +extern const idEventDef MA_EnemyIsPhysical; +extern const idEventDef MA_EnemyPortal; + +// +// hhNoClipEnt +// +// Dummy entity that sets contents to 0 - created for use for ForceAAS entities +// +class hhNoClipEnt : public idEntity { +public: + CLASS_PROTOTYPE(hhNoClipEnt); + + void Spawn( void ); +}; + +class hhAINode : public idEntity { +public: + CLASS_PROTOTYPE( hhAINode ); + hhAINode(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + idEntityPtr< idEntity> user; +}; + +//! Somewhat of a hack. This should in an a .h file someplace. +// Not in a .cpp file as id has it now. +//#define CONDITION( name ) name.PointTo( scriptObject, #name ) + +struct hhMonsterReaction { + hhMonsterReaction(void) : reactionIndex(-1) { } + inline hhReaction *GetReaction() const { return ( entity.IsValid() && reactionIndex >= 0 ? entity->GetReaction(reactionIndex) : NULL ); } + int reactionIndex; + idEntityPtr entity; +}; + +// +// hhMonsterHealthTrigger +// +struct hhMonsterHealthTrigger { + hhMonsterHealthTrigger() {healthThresh = 0; triggerEnt = NULL; triggerCount = 0;} + + int healthThresh; // When our health drops below this value, trigger our ent + idEntityPtr triggerEnt; // The entity to trigger + int triggerCount; // Number of times we have tripped this trigger +}; + +class hhMonsterAI : public idAI { + +public: + CLASS_PROTOTYPE(hhMonsterAI); + + hhMonsterAI(); + ~hhMonsterAI(); + virtual void Think(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + const char * GetJointForFrameCommand( const char *cmd ) { idStr tmp( cmd ); int i = tmp.Find( " " ); if ( i < 0 ) { return( va( "%s", cmd ) ); } else { return( va( "%s", tmp.Left( i ).c_str() ) ); } }; + void Spawn(); + virtual void LinkScriptVariables(void); + bool HasPathTo(const idVec3 &destPt); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void EnterVehicle( hhVehicle* vehicle ); + virtual bool TestMelee( void ) const; + virtual bool MoveToPosition( const idVec3 &pos, bool enemyBlocks = false ); + virtual bool TurnToward( const idVec3 &pos ); + ID_INLINE virtual bool TurnToward( float yaw ) { return idAI::TurnToward( yaw ); } // HUMANHEAD mdl: Needed because of bizarre inheritance issue that resulted in TurnToward(idVec3) being called + virtual bool CanSee( idEntity *ent, bool useFov ); + idPlayer* GetClosestPlayer( void ); + void Show(); + bool GetFacePosAngle( const idVec3 &pos, float &delta ); + virtual void SetEnemy( idActor *newEnemy ); //made public because hhAI does + void Pickup( hhPlayer *player ); + bool GiveToPlayer( hhPlayer* player ); + idVec3 GetTouchPos(idEntity *ent, const hhReactionDesc *desc ); + void UpdateFromPhysics( bool moveBack ); + virtual bool GetTouchPosBound( const hhReactionDesc *desc, idBounds& bounds ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void CreateHealthTriggers(); + void UpdateHealthTriggers(int oldHealth, int currHealth); + int ReactionTo( const idEntity *ent ); + virtual bool UpdateAnimationControllers( void ); + virtual bool ShouldTouchTrigger( idEntity* entity ) const { return fl.touchTriggers; } + virtual void FlyTurn( void ); + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void UpdateEnemyPosition( void ); + void BecameBound(hhBindController *b); + void BecameUnbound(hhBindController *b); + void UpdateModelTransform(); + virtual // HUMANHEAD CJR + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + hhEntityFx* SpawnFxLocal( const char *fxName, const idVec3 &origin, const idMat3& axis = mat3_identity, const hhFxInfo* const fxInfo = NULL, bool forceClient = false ); + bool AttackMelee( const char *meleeDefName ); + bool TestMeleeDef( const char *meleeDefName ) const; + virtual void PrintDebug(); + int GetTurnDir(); + bool FacingEnemy( float range ); + virtual void Activate( idEntity *activator ); + void BecomeActive( int flags ); + bool OverrideKilledByGravityZones() { return bOverrideKilledByGravityZones; } + virtual void Hide(); + virtual void HideNoDormant(); + void AddLocalMatterWound( jointHandle_t jointNum, const idVec3 &localOrigin, const idVec3 &localNormal, const idVec3 &localDir, int damageDefIndex, const idMaterial *collisionMaterial ); + ID_INLINE virtual bool IsDead() { return AI_DEAD != 0; } + bool Pushes() { return( pushes && move.moveCommand != MOVE_NONE ); } // only push if moving + void GravClipModelAxis( bool enable ) { physicsObj.SetGravClipModelAxis( enable ); } + virtual void Distracted( idActor *newEnemy ); + +// Events. + virtual void Event_PostSpawn(); + void Event_AttackMissileEx(const char *params, int boneDir); + virtual void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ); + void Event_SetMeleeRange( float newMeleeRange ); + virtual void Event_FindReaction( const char* effect ); + virtual void Event_UseReaction(); + void Event_EnemyOnSide(); + void Event_HitCheck( idEntity *ent, const char *animname ); + void Event_CreateMonsterPortal(); + void Event_GetShootTarget(); + void Event_TriggerReactEnt(); + virtual void Event_Remove( void ); + void Event_OnProjectileLaunch(hhProjectile *proj); + void Event_InitialWallwalk( void ); + void Event_GetVehicle(); + void Event_EnemyAimingAtMe(); + void Event_ReachedEntity( idEntity *ent ); + void Event_EnemyOnSpawn(); + void Event_SpawnFX( char *fxFile ); + void Event_SplashDamage(char *damage); + void Event_SetVehicleState(); + void Event_TestAnimMoveTowardEnemy( const char *animname ); + void Event_GetLastReachableEnemyPos(); + void Event_FollowPath( const char *pathName ); + void Event_EnemyIsA( const char* testclass ); + void Event_Subtitle( idList* parmList ); + void Event_SubtitleOff(); + void Event_EnableHeadlook(); + void Event_DisableHeadlook(); + void Event_EnableEyelook(); + void Event_DisableEyelook(); + void Event_FacingEnemy(float range); + void Event_BossBar( int onOff ); + virtual void Event_GetCombatNode(); + void Event_FallNow(); + void Event_AllowFall( int allowFall ); + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ); + void Event_InPlayerFov(); + void Event_IsRagdoll(); + void Event_MoveDone(); + void Event_SetShootTarget(idEntity *ent); + void Event_EnemyInGravityZone(void); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_LookAtEnemy( float duration ); + void Event_SetLookOffset( idAngles const &ang ); + void Event_SetHeadFocusRate( float rate ); + void Event_HeardSound( int ignore_team ); + virtual void Event_FlyZip(); + void Event_UseConsole( idEntity *ent ); + void Event_TestMeleeDef( const char *meleeDefName ) const; + void Event_EnemyInVehicle(); + void Event_EnemyOnWallwalk(); + void Event_AlertAI( idEntity *ent, float radius ); + void Event_TestAnimMoveBlocked( const char *animname ); + void Event_InGravityZone(); + void Event_StartSoundDelay( const char *soundName, int channel, int netSync, float delay ); + void Event_SetTeam( int new_team ); + virtual void Event_GetAttackPoint(); + void Event_HideNoDormant(); + void Event_SoundOnModel(); + void Event_ActivatePhysics(); + void Event_IsVehicleDocked(); + void Event_SetNeverDormant( int enable ); + void Event_EnemyInSpirit(); + void Event_EnemyPortal( idEntity *ent ); + + // CJR DDA TEST + int numDDADamageSamples; + float totalDDADamage; + + void DamagedPlayer( int damage ) { numDDADamageSamples++; totalDDADamage += damage; } + void SendDamageToDDA(); + // END CJR DDA TEST + + //passageway code + bool IsInsidePassageway(void) const {return currPassageway != NULL;} + hhAIPassageway* GetCurrPassageway(void) {return currPassageway.GetEntity();} +public: + idScriptBool AI_HAS_RANGE_ATTACK; // TRUE if this monster has a range attack (required for reaction system) + idScriptBool AI_HAS_MELEE_ATTACK; // TRUE if this monster has a melee attack (required for reaction system) + idScriptBool AI_USING_REACTION; // TRUE if monster is currently using reaction + idScriptBool AI_REACTION_FAILED; // TRUE if the last reaction attempt failed (path blocked, exclusive problem, etc) + idScriptBool AI_REACTION_ANIM; // TRUE if the current reaction is using an anim to 'cause' + idScriptBool AI_BACKWARD; + idScriptBool AI_STRAFE_LEFT; + idScriptBool AI_STRAFE_RIGHT; + idScriptBool AI_UPWARD; + idScriptBool AI_DOWNWARD; + idScriptBool AI_SHUTTLE_DOCKED; + idScriptBool AI_VEHICLE_ATTACK; + idScriptBool AI_VEHICLE_ALT_ATTACK; + idScriptBool AI_WALLWALK; + idScriptBool AI_FALLING; + idScriptBool AI_PATHING; + idScriptFloat AI_TURN_DIR; + idScriptBool AI_FLY_NO_SEEK; + idScriptBool AI_FOLLOWING_PATH; + + bool bNeverTarget; + static idList allSimpleMonsters; // Global list of all hhMonsterAI's currently created +protected: + bool NearEnoughTouchPos( idEntity* ent, const idVec3& targetPos, idBounds& bounds ); + bool CheckValidReaction(); + void FinishReaction( bool bFailed = false ); + virtual int EvaluateReaction( const hhReaction *react ); + bool NewWanderDir( const idVec3 &dest ); + void FlyMove(); + virtual void HandleNoGore(); + bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); +protected: + hhMonsterReaction targetReaction; // Info about our current reaction + idEntityPtr shootTarget; + idEntityPtr currPassageway; // The passage node this monster is currently in + idList healthTriggers; + int lastContactTime; + int hearingRange; + int nextSpeechTime; + idAngles lookOffset; + int nextTurnUpdate; + idVec3 spawnOrigin; //location of where this monster spawned + int spawnThinkFlags; + int fallDelay; + bool bCanFall; + bool bSeeThroughPortals; + bool bBossBar; + bool bBindOrient; //orient monster to its bindmaster + bool bCanWallwalk; + bool bOverrideKilledByGravityZones; + bool soundOnModel; + bool bBindAxis; + bool bCustomBlood; + bool bNoCombat; + int nextSpiritProxyCheck; +}; + + +#endif /* __PREY_MONSTER_AI_H__ */ + + diff --git a/neo/prey/Prey/game_monster_ai_events.cpp b/neo/prey/Prey/game_monster_ai_events.cpp new file mode 100644 index 00000000..8064dc7f --- /dev/null +++ b/neo/prey/Prey/game_monster_ai_events.cpp @@ -0,0 +1,1302 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef MA_AttackMissileEx("", "sd",NULL); +const idEventDef MA_SetMeleeRange("setMeleeRange", "f",NULL); +const idEventDef MA_FindReaction("findReaction", "s", 'E'); +const idEventDef MA_UseReaction("useReaction"); +const idEventDef MA_EnemyOnSide("enemyOnSide", NULL, 'f'); +const idEventDef MA_HitCheck( "hitCheck", "Es", 'd' ); +const idEventDef MA_CreateMonsterPortal("createMonsterPortal", NULL, 'f'); +const idEventDef MA_GetShootTarget("getShootTarget", NULL,'E'); +const idEventDef MA_TriggerReactEnt("triggerReactEnt"); +const idEventDef MA_InitialWallwalk(""); +const idEventDef MA_GetVehicle("getVehicle",NULL,'E'); +const idEventDef MA_EnemyAimingAtMe("enemyAimingAtMe",NULL,'d'); +const idEventDef MA_ReachedEntity("reachedEntity","e",'d'); +const idEventDef MA_EnemyOnSpawn("",NULL,NULL); +const idEventDef MA_SpawnFX( "spawnFX", "s" ); +const idEventDef MA_SplashDamage( "splashDamage", "s" ); +const idEventDef MA_SetVehicleState( "",NULL,NULL ); +const idEventDef MA_FollowPath( "followPath", "s", NULL ); +const idEventDef MA_GetLastReachableEnemyPos( "getLastReachableEnemyPos", "", 'v' ); +const idEventDef MA_OnProjectileLaunch("", "e"); +const idEventDef MA_EnemyIsA("enemyIsA", "s", 'd'); +const idEventDef MA_Subtitle("subtitle", "d"); +const idEventDef MA_SubtitleOff(""); +const idEventDef MA_EnableHeadlook("enableHeadlook"); +const idEventDef MA_DisableHeadlook("disableHeadlook"); +const idEventDef MA_EnableEyelook("enableEyelook"); +const idEventDef MA_DisableEyelook("disableEyelook"); +const idEventDef MA_FacingEnemy("facingEnemy", "f", 'd'); +const idEventDef MA_BossBar("bossBar", "d"); +const idEventDef MA_FallNow(""); +const idEventDef MA_AllowFall("allowFall", "d"); //HUMANHEAD jsh PCF 5/3/06 Changed parameter from "f" to "d" +const idEventDef MA_InPlayerFov("inPlayerFov", NULL, 'd'); +const idEventDef MA_EnemyIsSpirit("", "ee"); +const idEventDef MA_EnemyIsPhysical("", "ee"); +const idEventDef MA_IsRagdoll("isRagdoll", NULL, 'd'); +const idEventDef MA_MoveDone("moveDone", NULL, 'd'); +const idEventDef MA_SetShootTarget("setShootTarget", "E"); +const idEventDef EV_EnemyInGravityZone( "enemyInGravityZone", NULL, 'f' ); +const idEventDef MA_SetLookOffset( "setLookOffset", "v" ); +const idEventDef MA_SetHeadFocusRate( "setHeadFocusRate", "f" ); +const idEventDef MA_FlyZip( "flyZip" ); +const idEventDef MA_UseConsole( "useConsole", "e" ); +const idEventDef MA_TestMeleeDef("testMeleeDef", "s",'f'); +const idEventDef MA_EnemyInVehicle("enemyInVehicle", NULL, 'd'); +const idEventDef MA_EnemyOnWallwalk("enemyOnWallwalk", NULL, 'd'); +const idEventDef MA_AlertAI("alertAI", "ef" ); +const idEventDef MA_TestAnimMoveBlocked("testAnimMoveBlocked", "s", 'e'); +const idEventDef MA_InGravityZone("inGravityZone", NULL, 'd'); +const idEventDef MA_StartSoundDelay( "startSoundDelay", "sddf", 'f' ); +const idEventDef MA_SetTeam( "setTeam", "d" ); +const idEventDef MA_GetAttackPoint ("getAttackPoint", NULL, 'v'); +const idEventDef MA_HideNoDormant("hideNoDormant" ); +const idEventDef MA_SoundOnModel("soundOnModel"); +const idEventDef MA_ActivatePhysics("activatePhysics"); +const idEventDef MA_IsVehicleDocked("isVehicleDocked"); +const idEventDef MA_EnemyInSpirit( "enemyInSpirit", NULL, 'd' ); +const idEventDef MA_GetAttackNode( "getAttackNode", NULL, 'v' ); + +CLASS_DECLARATION(idAI, hhMonsterAI) + EVENT( MA_AttackMissileEx, hhMonsterAI::Event_AttackMissileEx ) + EVENT( MA_FindReaction, hhMonsterAI::Event_FindReaction ) + EVENT( MA_UseReaction, hhMonsterAI::Event_UseReaction ) + EVENT( MA_SetMeleeRange, hhMonsterAI::Event_SetMeleeRange ) + EVENT( MA_EnemyOnSide, hhMonsterAI::Event_EnemyOnSide ) + EVENT( MA_HitCheck, hhMonsterAI::Event_HitCheck ) + EVENT( MA_CreateMonsterPortal, hhMonsterAI::Event_CreateMonsterPortal ) + EVENT( MA_GetShootTarget, hhMonsterAI::Event_GetShootTarget ) + EVENT( MA_TriggerReactEnt, hhMonsterAI::Event_TriggerReactEnt ) + EVENT( MA_InitialWallwalk, hhMonsterAI::Event_InitialWallwalk ) + EVENT( MA_GetVehicle, hhMonsterAI::Event_GetVehicle ) + EVENT( MA_EnemyAimingAtMe, hhMonsterAI::Event_EnemyAimingAtMe ) + EVENT( MA_ReachedEntity, hhMonsterAI::Event_ReachedEntity ) + EVENT( MA_EnemyOnSpawn, hhMonsterAI::Event_EnemyOnSpawn ) + EVENT( MA_SpawnFX, hhMonsterAI::Event_SpawnFX ) + EVENT( MA_SplashDamage, hhMonsterAI::Event_SplashDamage) + EVENT( MA_SetVehicleState, hhMonsterAI::Event_SetVehicleState ) + EVENT( EV_PostSpawn, hhMonsterAI::Event_PostSpawn ) + EVENT( MA_FollowPath, hhMonsterAI::Event_FollowPath ) + EVENT( MA_GetLastReachableEnemyPos, hhMonsterAI::Event_GetLastReachableEnemyPos ) + EVENT( MA_EnemyIsA, hhMonsterAI::Event_EnemyIsA ) + EVENT( MA_Subtitle, hhMonsterAI::Event_Subtitle ) + EVENT( MA_SubtitleOff, hhMonsterAI::Event_SubtitleOff ) + EVENT( MA_EnableHeadlook, hhMonsterAI::Event_EnableHeadlook ) + EVENT( MA_DisableHeadlook, hhMonsterAI::Event_DisableHeadlook ) + EVENT( MA_EnableEyelook, hhMonsterAI::Event_EnableEyelook ) + EVENT( MA_DisableEyelook, hhMonsterAI::Event_DisableEyelook ) + EVENT( MA_FacingEnemy, hhMonsterAI::Event_FacingEnemy ) + EVENT( MA_BossBar, hhMonsterAI::Event_BossBar ) + EVENT( MA_FallNow, hhMonsterAI::Event_FallNow ) + EVENT( MA_AllowFall, hhMonsterAI::Event_AllowFall ) + EVENT( MA_InPlayerFov, hhMonsterAI::Event_InPlayerFov ) + EVENT( MA_EnemyIsSpirit, hhMonsterAI::Event_EnemyIsSpirit ) + EVENT( MA_EnemyIsPhysical, hhMonsterAI::Event_EnemyIsPhysical ) + EVENT( MA_IsRagdoll, hhMonsterAI::Event_IsRagdoll ) + EVENT( MA_MoveDone, hhMonsterAI::Event_MoveDone ) + EVENT( MA_SetShootTarget, hhMonsterAI::Event_SetShootTarget) + EVENT( EV_EnemyInGravityZone, hhMonsterAI::Event_EnemyInGravityZone ) + EVENT( MA_SetLookOffset, hhMonsterAI::Event_SetLookOffset ) + EVENT( MA_SetHeadFocusRate, hhMonsterAI::Event_SetHeadFocusRate ) + EVENT( MA_FlyZip, hhMonsterAI::Event_FlyZip ) + EVENT( MA_UseConsole, hhMonsterAI::Event_UseConsole ) + EVENT( MA_TestMeleeDef, hhMonsterAI::Event_TestMeleeDef ) + EVENT( MA_EnemyInVehicle, hhMonsterAI::Event_EnemyInVehicle ) + EVENT( MA_EnemyOnWallwalk, hhMonsterAI::Event_EnemyOnWallwalk ) + EVENT( MA_AlertAI, hhMonsterAI::Event_AlertAI ) + EVENT( MA_TestAnimMoveBlocked, hhMonsterAI::Event_TestAnimMoveBlocked ) + EVENT( MA_InGravityZone, hhMonsterAI::Event_InGravityZone ) + EVENT( MA_StartSoundDelay, hhMonsterAI::Event_StartSoundDelay ) + EVENT( MA_SetTeam, hhMonsterAI::Event_SetTeam ) + EVENT( MA_GetAttackPoint, hhMonsterAI::Event_GetAttackPoint) + EVENT( MA_HideNoDormant, hhMonsterAI::Event_HideNoDormant ) + EVENT( MA_SoundOnModel, hhMonsterAI::Event_SoundOnModel ) + EVENT( MA_ActivatePhysics, hhMonsterAI::Event_ActivatePhysics ) + EVENT( MA_IsVehicleDocked, hhMonsterAI::Event_IsVehicleDocked ) + EVENT( MA_EnemyInSpirit, hhMonsterAI::Event_EnemyInSpirit ) +END_CLASS + +void hhMonsterAI::Event_EnemyAimingAtMe() { + if ( enemy.IsValid() ) { + if ( enemy->GetAxis()[0] * -(enemy->GetOrigin() - GetOrigin()).ToNormal() ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +//A little after Spawn() or Show(), check if monster is stuck on wallwalk. +void hhMonsterAI::Event_InitialWallwalk() { + if ( IsHidden() ) { + return; + } + idMat3 initRot = spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1" ); + if ( initRot == mat3_identity ) { + return; + } + trace_t TraceInfo; + gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), GetOrigin() - initRot[2]*100, GetPhysics()->GetClipMask(), this); + if( TraceInfo.fraction < 1.0f && health > 0 ) { + if ( gameLocal.GetMatterType(TraceInfo, NULL) == SURFTYPE_WALLWALK ) { + DisableIK(); + SetOrigin( TraceInfo.c.point + initRot * idVec3(0,0,1) ); + GetPhysics()->SetAxis( initRot ); + viewAxis = initRot; + SetGravity( -TraceInfo.c.normal * 1066 ); + physicsObj.SetClipModelAxis(); + renderEntity.axis = initRot; + + idVec3 local_dir; + physicsObj.GetAxis().ProjectVector( initRot[0], local_dir ); + ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + current_yaw = ideal_yaw; + } + } +} + +void hhMonsterAI::Event_EnemyOnSide() { + if( enemy.IsValid() ) { + idVec3 povPos, targetPos; + povPos = enemy->GetPhysics()->GetOrigin(); + targetPos = GetPhysics()->GetOrigin(); + idVec3 povToTarget = targetPos - povPos; + povToTarget.z = 0.f; + povToTarget.Normalize(); + float dot = GetPhysics()->GetAxis()[ 1 ] * povToTarget; + idThread::ReturnFloat( dot ); + } + else { + idThread::ReturnFloat( 0.f ); + } +} + +void hhMonsterAI::Event_EnemyIsA( const char* testclass ) { + idTypeInfo* type = idClass::GetClass( testclass ); + if( type ) { + if( enemy.IsValid() ) { + if( enemy->GetType()->IsType(*type) ) { + idThread::ReturnInt( 1 ); + return; + } + } + } + idThread::ReturnInt( 0 ); +} + +void hhMonsterAI::Event_SetMeleeRange( float newRange ) { + melee_range = newRange; +} + +// +// Event_UseReaction +// +// Notes: +// + Need to support the actual various types of Causes, not just this one specific case. +// + Need to support blends (in/out) on animation causes +void hhMonsterAI::Event_UseReaction() { + if( !AI_USING_REACTION ) { + AI_USING_REACTION = true; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + } + + if( !CheckValidReaction() ) { + AI_USING_REACTION = false; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + return; + } + + hhReaction *reaction = targetReaction.GetReaction(); + assert(reaction); // CheckValidReaction() should ensure this is a valid pointer. + if( reaction->desc->flags & hhReactionDesc::flag_Exclusive ) { + if ( !reaction->exclusiveOwner.IsValid() ) { + reaction->exclusiveOwner = this; + } else if ( reaction->exclusiveOwner != this ) { + AI_USING_REACTION = false; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + return; + } + } + + idVec3 tp = GetTouchPos( reaction->causeEntity.GetEntity(), reaction->desc ); + idBounds tpb; + bool validTpb = GetTouchPosBound( reaction->desc, tpb ); + + if( AI_REACTION_ANIM ) { + if( torsoAnim.AnimDone( 0 ) || GetAnimator()->CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0.f ) { + AI_REACTION_ANIM = false; + Event_AnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + Event_AnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + ProcessEvent( &AI_EnablePain ); + FinishReaction(); + } + } + else { + //play reaction sound if one exists + if ( !ai_skipSpeech.GetBool() && gameLocal.GetTime() > nextSpeechTime ) { + idStr speechDesc = idStr("snd_speech_"); + speechDesc += hhReactionDesc::EffectToStr( reaction->desc->effect ); + speechDesc += idStr( "_" ); + speechDesc += targetReaction.entity->spawnArgs.GetString( "speech_name" ); + if ( ai_debugBrain.GetBool() ) { + gameLocal.Printf( "reaction speech(%s)\n", speechDesc.c_str() ); + } + const idKeyValue *kv = spawnArgs.MatchPrefix( speechDesc ); + if( kv ) { + StartSound(kv->GetKey(), SND_CHANNEL_VOICE, 0, true, NULL); + } + nextSpeechTime = gameLocal.GetTime() + int(spawnArgs.GetFloat( "speech_wait", "2.0" ) * 1000); + } + if( (validTpb && !NearEnoughTouchPos(reaction->causeEntity.GetEntity(), tp, tpb)) || (!validTpb && !ReachedPos(tp, move.moveCommand)) ) { + if ( !MoveToPosition( tp, true ) ) { + //position is unreachable so stop using this reaction + AI_USING_REACTION = false; + AI_REACTION_FAILED = false; + AI_REACTION_ANIM = false; + } + } else { + SlideToPosition( tp, 0.5 ); + StopMove( MOVE_STATUS_DONE ); + if( reaction->desc->flags & hhReactionDesc::flag_SnapToPoint ) { + SetOrigin( idVec3(tp.x, tp.y, tp.z) ); + } + //Turn to face direction specified by node + if( reaction->causeEntity->spawnArgs.GetFloat("face_dir") ) { + idAngles faceAngles = reaction->causeEntity->GetAxis()[0].ToAngles(); + ideal_yaw = faceAngles.yaw; + current_yaw = faceAngles.yaw; + SetAxis( reaction->causeEntity->GetAxis() ); + } + else if( reaction->desc->flags & hhReactionDesc::flag_AnimFaceCauseDir ) { + idAngles faceAngles = ( reaction->causeEntity->GetOrigin() - GetOrigin() ).ToAngles(); + ideal_yaw = faceAngles.yaw; + current_yaw = faceAngles.yaw; + idAngles newAngles = GetAxis().ToAngles(); + newAngles.yaw = faceAngles.yaw; + SetAxis( newAngles.ToMat3() ); + } + idEntity *ent = NULL; + switch( reaction->desc->cause ) { + case hhReactionDesc::Cause_Touch: + FinishReaction(); + break; + case hhReactionDesc::Cause_Use: + ent = targetReaction.entity.GetEntity(); + if( ent && ent->IsType(hhConsole::Type)) { + hhConsole *cons = static_cast(ent); + if(!cons->CanUse(this)) { + return; + } + cons->Use(this); + } + // Using vehicle -- just bail! + else if(ent->IsType(hhVehicle::Type)) { + EnterVehicle( static_cast(ent) ); + FinishReaction(); + return; + } else { + gameLocal.Warning("\n AI TRIED TO USE UNUSABLE ENTITY!"); + } + FinishReaction(); + break; + case hhReactionDesc::Cause_PlayAnim: + AI_REACTION_ANIM = true; + GetAnimator()->Clear( ANIMCHANNEL_TORSO, gameLocal.GetTime(), 0 ); + GetAnimator()->Clear( ANIMCHANNEL_TORSO, gameLocal.GetTime(), 0 ); + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + Event_AnimState( ANIMCHANNEL_TORSO, "Torso_DoNothing", 0 ); + Event_AnimState(ANIMCHANNEL_LEGS, "Legs_DoNothing", 0 ); + Event_OverrideAnim( ANIMCHANNEL_LEGS ); + Event_PlayAnim( ANIMCHANNEL_TORSO, reaction->desc->anim ); + ProcessEvent(&AI_DisablePain); + break; + default: + break; + } + } + } +} + +// +// Event_FindReaction +// +void hhMonsterAI::Event_FindReaction( const char* effect ) { + idEntity* ent; + hhReaction* react; + hhReactionDesc::Effect react_effect; + idEntity* bestEnt = NULL; + float bestDistance = -1; + int bestRank = -1, bestReactIndex = -1; + + react_effect = hhReactionDesc::StrToEffect( effect ); + + if( react_effect == hhReactionDesc::Effect_Invalid ) { + gameLocal.Warning( "unknown effect '%s' requested from FindReaction", effect ); + idThread::ReturnEntity( NULL ); + return; + } + + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if( !ent || ent->fl.isDormant || !ent->fl.refreshReactions ) { + continue; + } + + for( int j = 0; j < ent->GetNumReactions(); j++ ) { + react = ent->GetReaction( j ); + if( !react || !react->IsActive() ) { + continue; + } + if( react_effect != react->desc->effect ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flag_Exclusive ) { //check exclusiveness + if ( react->exclusiveOwner.IsValid() && react->exclusiveOwner->health <= 0 ) { + react->exclusiveOwner.Clear(); + } + if( react->exclusiveOwner.GetEntity() && react->exclusiveOwner != this ) { + continue; + } + } + //Skip based on flag requirements + if( (react->desc->flags & hhReactionDesc::flagReq_RangeAttack) && !AI_HAS_RANGE_ATTACK ) { + continue; + } + // Skip monsters without melee attack + if( (react->desc->flags & hhReactionDesc::flagReq_MeleeAttack) && !AI_HAS_MELEE_ATTACK ) { + continue; + } + if( react->desc->flags & hhReactionDesc::flagReq_KeyValue ) { + idStr val; + if( spawnArgs.GetString(react->desc->key, "", val) ) { + if( val != react->desc->keyVal ) { + continue; + } + } + else { + continue; + } + } + // Skip monsters without specific animation? + if( react->desc->flags & hhReactionDesc::flagReq_Anim ) { + if( !GetAnimator()->HasAnim(react->desc->anim) ) { + continue; + } + } + + // Skip monsters in vehicles? + if( (react->desc->flags & hhReactionDesc::flagReq_NoVehicle) && InVehicle() ) { + continue; + } + + // Check actual specifics for reaction type + switch( react->desc->effect ) { + case hhReactionDesc::Effect_HaveFun: + case hhReactionDesc::Effect_Vehicle: + case hhReactionDesc::Effect_VehicleDock: + case hhReactionDesc::Effect_Heal: + case hhReactionDesc::Effect_ProvideCover: + case hhReactionDesc::Effect_Climb: + case hhReactionDesc::Effect_Passageway: + break; + case hhReactionDesc::Effect_DamageEnemy: + if ( enemy.IsValid() && react->desc->effectRadius > 0 ) { + float distSq = (enemy->GetOrigin() - react->causeEntity->GetOrigin()).LengthSqr(); + if ( distSq > react->desc->effectRadius * react->desc->effectRadius ) { + continue; + } + } + break; + case hhReactionDesc::Effect_Damage: + //jshtodo temp workaround for legacy stuff. remove when old reaction system is removed + if ( !ent->IsType( hhConsole::Type ) ) { + continue; + } + break; + default: + gameLocal.Error( "effect '%s' not supported yet under simple_ai", hhReactionDesc::EffectToStr(react->desc->effect) ); + } + // if we have actually gotten this far, do our intense calculations last.. + int rank = EvaluateReaction( react ); + if( rank == 0 ) { + continue; + } + +// We have a valid reaction... + float distSq = ( react->causeEntity->GetOrigin() - GetOrigin() ).LengthSqr(); + if ( bestRank == -1 || bestRank <= rank ) { // Check the reaction rank against our best rank + if (rank == bestRank && distSq > bestDistance) { // If they are the same rank, but the current one is farther then ignore it. + continue; + } + bestEnt = ent; + bestReactIndex = j; + bestDistance = distSq; + bestRank = rank; + } + } + } + + if ( bestEnt ) { + targetReaction.entity = bestEnt; + targetReaction.reactionIndex = bestReactIndex; + hhReaction *reaction = targetReaction.GetReaction(); + if( reaction && reaction->desc->flags & hhReactionDesc::flagReq_RangeAttack ) { + shootTarget = bestEnt; + } else { + shootTarget = NULL; + } + idThread::ReturnEntity( targetReaction.entity.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +// +// Event_AttackMissileEx() +// +void hhMonsterAI::Event_AttackMissileEx( const char *str, int boneDir ) { + const idDict *projDef = NULL; + idList parmList; + + hhUtils::SplitString( idStr(str), parmList, ' ' ); + + if( animator.GetJointHandle(parmList[0].c_str()) == INVALID_JOINT ) { + return gameLocal.Error( "Event_AttackMissileEx: Joint '%s' not found", parmList[0].c_str() ); + } + + if( parmList.Num() == 2 ) { + projDef = gameLocal.FindEntityDefDict( parmList[1].c_str(), false ); + } + + Event_AttackMissile( parmList[0].c_str(), projDef, boneDir ); +} + +//HUMANHEAD jsh PCF 4/27/06 initialized proj and made sure ReturnEntity is called +void hhMonsterAI::Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) { + idProjectile *proj = NULL; + + // Bonedir launch? + if((BOOL)boneDir) { + proj = hhProjectile::SpawnProjectile(projDef); + if ( proj ) { + idMat3 axis; + idVec3 muzzle; + GetMuzzle( jointname, muzzle, axis ); + proj->Create(this, muzzle, axis); + proj->Launch(muzzle, axis, vec3_zero); + } + } + else { + if ( shootTarget.IsValid() ) { + proj = LaunchProjectile( jointname, shootTarget.GetEntity(), true, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } else { + proj = LaunchProjectile( jointname, enemy.GetEntity(), true, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support + } + } + idThread::ReturnEntity( proj ); +} + +void hhMonsterAI::Event_HitCheck( idEntity *ent, const char *animname ) { + int anim; + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + trace_t tr; + float distance; + + if ( !AI_ENEMY_VISIBLE || !ent ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + idThread::ReturnInt( false ); + return; + } + + //// just do a ray test if close enough + //if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + // Event_CanHitEnemy(); + // return; + //} + + // calculate the world transform of the launch position + const idVec3 &org = physicsObj.GetOrigin(); + dir = ent->GetOrigin() - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis; + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + // check if the owner bounds is bigger than the projectile bounds + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + const idBounds &projBounds = projectileClipModel->GetBounds(); + 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(); + } + + gameLocal.clip.Translation( tr, start, fromPos, projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this ); + fromPos = tr.endpos; + + if ( GetAimDir( fromPos, ent, this, dir ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +void hhMonsterAI::Event_CreateMonsterPortal(void) { + static const char * passPrefix = "portal_"; + const char * portalDef; + idEntity * portal; + idDict portalArgs; + idList xferKeys; + idList xferValues; + const idKeyValue * buddyKV; + + + // Find out which portal def to spawn - If none specified, then exit; + buddyKV = spawnArgs.FindKey( "portal_buddy" ); + if ( buddyKV && buddyKV->GetValue().Length() && gameLocal.FindEntity(buddyKV->GetValue().c_str()) ) { + // Case of a valid portal_buddy key, make a real portal + portalDef = spawnArgs.GetString( "def_portal" ); + } else { + portalDef = spawnArgs.GetString( "def_fakeportal" ); + } + + if ( !portalDef || !portalDef[0] ) { + return; + } + + // Set the origin of the portal to us. + portalArgs.SetVector( "origin", GetOrigin() ); + + // Pass along any angle key, if set. + if ( spawnArgs.GetBool( "portal_face" ) && enemy.IsValid() ) { + portalArgs.SetInt( "angle", (enemy->GetOrigin() - GetOrigin() ).ToAngles().yaw ); + ideal_yaw = ( enemy->GetOrigin() - GetOrigin() ).ToAngles().yaw; + current_yaw = ideal_yaw; + } else if ( spawnArgs.GetString( "angle", NULL) ) { + portalArgs.Set( "angle", spawnArgs.GetString( "angle" ) ); + } else { + portalArgs.Set( "rotation", spawnArgs.GetString( "rotation" ) ); + } + + // Pass along all 'portal_' keys to the portal's spawnArgs; + hhUtils::GetKeysAndValues( spawnArgs, passPrefix, xferKeys, xferValues ); + for ( int i = 0; i < xferValues.Num(); ++i ) { + xferKeys[ i ].StripLeadingOnce( passPrefix ); + //gameLocal.Printf( "Passing %s => %s\n", xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + portalArgs.Set( xferKeys[ i ].c_str(), xferValues[ i ].c_str() ); + } + + // Set the name of the associated game portal so it can be turned on and off + portalArgs.Set( "gamePortalName", GetName() ); + + // Spawn the portal + portal = gameLocal.SpawnObject( portalDef, &portalArgs ); + if ( !portal ) { + return; + } + + // Move the portal up some pre determinted amt, since its origin is in the middle of it + float offset = spawnArgs.GetFloat( "offset_portal", 0 ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis()[2] * offset) ); + + // Move the portal by some offset vector + idVec3 vectorOffset = spawnArgs.GetVector( "offset_portal_vector", "0 0 0" ); + portal->GetPhysics()->SetOrigin( portal->GetPhysics()->GetOrigin() + (portal->GetAxis() * vectorOffset ) ); + + // Update the camera stuff + portal->ProcessEvent( &EV_UpdateCameraTarget ); + + // Open the portal - Need to delay this, so that PostSpawn gets called/sets up the partner portal + //? Should we always pass in the player? + portal->PostEventSec( &EV_Activate, 0, gameLocal.GetLocalPlayer() ); + // Maybe wait for it to finish? + + +} + +void hhMonsterAI::Event_GetShootTarget() { + idThread::ReturnEntity( shootTarget.GetEntity() ); +} + +void hhMonsterAI::Event_TriggerReactEnt(void) { + if ( targetReaction.entity.IsValid() ) { + targetReaction.entity->PostEventMS( &EV_Activate, 0, this ); + } +} + +void hhMonsterAI::Event_GetVehicle() { + idThread::ReturnEntity( vehicleInterface->GetVehicle() ); +} + +void hhMonsterAI::Event_ReachedEntity( idEntity *ent ) { + if ( ent ) { + idVec3 pos = ent->GetPhysics()->GetOrigin(); + if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_EnemyOnSpawn(void) +{ + idEntity *ent = NULL; + idStr entName; + if(spawnArgs.GetString("enemy_on_spawn"," ", entName)) + { + if ( entName == idStr("closest_player") || entName == idStr("players_in_view") ) { + ent = GetClosestPlayer(); + } + if ( !ent ) { + ent = gameLocal.FindEntity(entName); + } + if( ent && ent->IsType(idActor::Type) ) { + SetEnemy( static_cast(ent) ); + } + } +} + +void hhMonsterAI::Event_SpawnFX( char *fxFile ) { + + if ( !fxFile || !fxFile[0] ) { + return; + } + + idVec3 offset; + hhFxInfo fxInfo; + + offset = spawnArgs.GetVector( "particle_offset", "0 0 0" ); + + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( fxFile, GetOrigin() + offset * GetAxis(), GetAxis(), &fxInfo ); + +} + +void hhMonsterAI::Event_SplashDamage(char *damage) { + + if(!damage || !damage[0]) + return; + + idStr splash_damage; + + // Need to set takedamage to false in order to prevent infinite loop! + //fl.takedamage = false; + splash_damage = spawnArgs.GetString(damage); + if ( splash_damage.Length() ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), NULL, NULL, this, this, splash_damage ); + } +} + +void hhMonsterAI::Event_FollowPath( const char *pathName ) { + if ( scriptObject.HasObject() ) { + const function_t *func; + idThread *thread; + + func = scriptObject.GetFunction( "follow_alternate_path" ); + if ( !func ) { + gameLocal.Error( "Function 'follow_alternate_path' not found on entity '%s' for function call from '%s'", name.c_str(), name.c_str() ); + } + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function 'follow_alternate_path' on entity '%s' has the wrong number of parameters for function call from '%s'", name.c_str(), name.c_str() ); + } + if ( !scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function 'follow_alternate_path' on entity '%s' is the wrong type for function call from '%s'", name.c_str(), name.c_str() ); + } + spawnArgs.Set( "alt_path", pathName ); + // create a thread and call the function + thread = new idThread(); + thread->CallFunction( this, func, true ); + thread->Start(); + } +} + +void hhMonsterAI::Event_SetVehicleState() { + const function_t *newstate = NULL; + if ( GetVehicleInterface()->UnderScriptControl() ) { + newstate = GetScriptFunction( "state_Nothing" ); + } + if ( newstate ) { + SetState( newstate ); + } +} + +void hhMonsterAI::Event_Subtitle( idList* parmList ) { + if ( !parmList || !parmList->Num() ) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + player->hud->SetStateInt("subtitlex", 0 ); + player->hud->SetStateInt("subtitley", 400 ); + player->hud->SetStateInt("subtitlecentered", true); + player->hud->SetStateString("subtitletext", common->GetLanguageDict()->GetString( (*parmList)[0].c_str() )); + player->hud->StateChanged(gameLocal.time); + player->hud->HandleNamedEvent("DisplaySubtitle"); + + CancelEvents(&MA_SubtitleOff); + if ( parmList->Num() == 1 ) { + PostEventSec(&MA_SubtitleOff, (float)atof((*parmList)[1].c_str())); + } else { + PostEventSec(&MA_SubtitleOff, 2.0); + } +} + +void hhMonsterAI::Event_SubtitleOff() { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->hud->HandleNamedEvent("RemoveSubtitleInstant"); +} + +/* +===================== +hhMonsterAI::Event_GetLastReachableEnemyPos +===================== +*/ +void hhMonsterAI::Event_GetLastReachableEnemyPos() { + idThread::ReturnVector( lastReachableEnemyPos ); +} + +void hhMonsterAI::Event_EnableEyelook() { + allowEyeFocus = true; +} + +void hhMonsterAI::Event_DisableEyelook() { + allowEyeFocus = false; +} + +void hhMonsterAI::Event_EnableHeadlook() { + allowJointMod = true; +} + +void hhMonsterAI::Event_DisableHeadlook() { + allowJointMod = false; +} + +void hhMonsterAI::Event_FacingEnemy( float range ) { + if ( FacingEnemy( range ) ) { + idThread::ReturnInt( 1 ); + } else { + idThread::ReturnInt( 0 ); + } +} + +bool hhMonsterAI::FacingEnemy( float range ) { + if ( !enemy.IsValid() ) { + return false; + } + + if ( !turnRate ) { + return true; + } + + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + float local_yaw; + + dir = enemy->GetOrigin() - physicsObj.GetOrigin(); + physicsObj.GetAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + local_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + + float diff; + + diff = idMath::AngleNormalize180( current_yaw - local_yaw ); + return ( idMath::Fabs( diff ) < range ); +} + +void hhMonsterAI::Event_AllowFall( int allowFall ) { + bCanFall = allowFall != 0; +} + +void hhMonsterAI::Event_BossBar( int onOff ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->hud ) { + return; + } + + if ( bBossBar ) { + if ( !onOff ) { + player->hud->HandleNamedEvent("HideProgressBar"); + player->hud->StateChanged(gameLocal.time); + bBossBar = false; + } + } else { + if ( onOff ) { + player->hud->HandleNamedEvent("ShowProgressBar"); + player->hud->SetStateBool( "progressbar", true ); + bBossBar = true; + } + } +} + +void hhMonsterAI::Event_FallNow() { + SetGravity( DEFAULT_GRAVITY_VEC3 ); + GetPhysics()->SetLinearVelocity( idVec3( 0,0,-500 ) ); +} + +//taken from idEntity::Event_PlayerCanSee and removed trace +void hhMonsterAI::Event_InPlayerFov() { + int i; + idEntity *ent; + hhPlayer *player; + trace_t traceInfo; + bool result = false; + + // Check if this entity is in the player's PVS + if ( gameLocal.InPlayerPVS( this ) ) { + for ( i = 0; i < gameLocal.numClients ; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( hhPlayer::Type ) ) { + continue; + } + + // Get the player + player = static_cast( ent ); + + // Check if the entity is in the player's FOV, based upon the "fov" key/value + if ( player->CheckYawFOV( this->GetOrigin() ) ) { + result = true; + } + } + } + + if ( result ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +void hhMonsterAI::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + HH_ASSERT( player == enemy.GetEntity() ); + //HUMANHEAD jsh PCF 4/29/06 stop looking and make enemy not visible for a frame + Event_LookAtEntity( NULL, 0.0f ); + AI_ENEMY_VISIBLE = false; + enemy = proxy; +} + +void hhMonsterAI::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) { + // If we weren't targetting the physical player before, and we can't see them now, + // lose target + //HUMANHEAD jsh PCF 4/29/06 stop looking and make enemy not visible for a frame + Event_LookAtEntity( NULL, 0.0f ); + AI_ENEMY_VISIBLE = false; + if ( !proxy && !CanSee( player, true ) ) { + enemy = NULL; + } else { + enemy = player; + } +} + +void hhMonsterAI::Event_IsRagdoll() { + if ( af.IsActive() ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_MoveDone() { + if ( AI_MOVE_DONE ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_SetShootTarget(idEntity *ent) { + shootTarget = ent; +} + +void hhMonsterAI::Event_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; + } + + if ( duration == -1 ) { + focusTime = -1; + } else { + focusTime = gameLocal.time + SEC2MS( duration ); + } +} + +void hhMonsterAI::Event_LookAtEnemy( float duration ) { + idActor *enemyEnt; + + enemyEnt = enemy.GetEntity(); + if ( ( enemyEnt != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = enemyEnt; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + if ( duration == -1 ) { + focusTime = -1; + } else { + focusTime = gameLocal.time + SEC2MS( duration ); + } +} + +void hhMonsterAI::Event_SetLookOffset( idAngles const &ang ) { + lookOffset = ang; +} + +void hhMonsterAI::Event_SetHeadFocusRate( float rate ) { + headFocusRate = rate; +} + +void hhMonsterAI::Event_EnemyInGravityZone(void) { + if ( !enemy.IsValid() ) { + idThread::ReturnFloat( 1.0f ); + return; + } + idThread::ReturnFloat( enemy->InGravityZone() ); +} + +void hhMonsterAI::Event_FlyZip() { + //if enemy is real far away, instantly teleport to a spot near him + if ( !enemy.IsValid() || move.moveType != MOVETYPE_FLY ) { + return; + } + + //find a good spot to try and teleport to + float distance = spawnArgs.GetFloat( "zip_range", "700" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + idBounds bounds; + idVec3 size; + bounds.Zero(); + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + } 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 ); + } else { // Default bounds + bounds.Expand( 1.0f ); + } + + //test 8 points around the enemy, starting with one directly in front of it + for ( int i=0;i<8;i++ ) { + testPoint = enemy->GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + yaw += 45; + if ( yaw > 360 ) { + yaw -= 360; + } + + int contents = hhUtils::ContentsOfBounds( bounds, testPoint, mat3_identity, this); + if ( contents & CONTENTS_MONSTERCLIP ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, GetOrigin(), testPoint, 10, 999999 ); + } + continue; + } + + //make sure we can path there + int toAreaNum = PointReachableAreaNum( testPoint ); + int areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + aasPath_t path; + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, testPoint ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, GetOrigin(), testPoint, 10, 10000 ); + } + continue; + } + + //passed all tests use this point + finalPoint = testPoint; + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), testPoint, 10, 10000 ); + } + break; + } + + if ( finalPoint != vec3_zero ) { + //do the actual teleport + GetPhysics()->SetOrigin( testPoint + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + viewAxis = (enemy->GetOrigin() - GetOrigin()).ToMat3(); + UpdateVisuals(); + } +} + +void hhMonsterAI::Event_UseConsole( idEntity *ent ) { + if ( !ent || !ent->IsType(hhConsole::Type) ) { + return; + } + hhConsole *cons = static_cast(ent); + if(!cons->CanUse(this)) { + return; + } + cons->Use(this); +} + +void hhMonsterAI::Event_TestMeleeDef( const char *meleeDefName ) const { + bool canMelee = TestMeleeDef( meleeDefName ); + + if ( canMelee ) { + idThread::ReturnFloat( 1.0f ); + } else { + idThread::ReturnFloat( 0.0f ); + } +} + +void hhMonsterAI::Event_EnemyInVehicle() { + if ( enemy.IsValid() && enemy->InVehicle() ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +void hhMonsterAI::Event_HeardSound( int ignore_team ) { + // overridden to use hearingRange instead of AI_HEARING_RANGE + // check if we heard any sounds in the last frame + idActor *actor = gameLocal.GetAlertEntity(); + 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(); + //allow the sound's own radius to override hearingRange, if it is set + if ( gameLocal.lastAIAlertRadius ) { + if ( dist < Square( gameLocal.lastAIAlertRadius ) ) { + idThread::ReturnEntity( actor ); + return; + } + } else if ( dist < Square( hearingRange ) ) { + idThread::ReturnEntity( actor ); + return; + } + } +} + +void hhMonsterAI::Event_AlertAI( idEntity *ent, float radius ) { + if ( !ent || radius <= 0 ) { + return; + } + gameLocal.AlertAI( ent, radius ); +} + +void hhMonsterAI::Event_EnemyOnWallwalk() { + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(enemy.GetEntity()); + if ( player && player->IsWallWalking() ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +/* +===================== +hhMonsterAI::Event_TestAnimMoveBlocked +===================== +*/ +void hhMonsterAI::Event_TestAnimMoveBlocked( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + + 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, ideal_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 ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + HH_ASSERT( path.endEvent == 0 && !path.blockingEntity || path.blockingEntity ); + + idThread::ReturnEntity( const_cast ( path.blockingEntity ) ); +} + +void hhMonsterAI::Event_InGravityZone() { + idThread::ReturnFloat( InGravityZone() ); +} + +void hhMonsterAI::Event_StartSoundDelay( const char *soundName, int channel, int netSync, float delay ) { + if ( delay < 0 ) { + delay = 0; + } + PostEventSec( &EV_StartSound, delay, soundName, channel, netSync ); +} + +void hhMonsterAI::Event_SetTeam( int new_team ) { + team = new_team; +} + +void hhMonsterAI::Event_HideNoDormant() { + HideNoDormant(); +} + +void hhMonsterAI::Event_GetAttackPoint( void ) { + //if enemy is real far away, instantly teleport to a spot near him + if ( !enemy.IsValid() ) { + idThread::ReturnVector( vec3_zero ); + return; + } + + //find a good spot to attack from + float distance = spawnArgs.GetFloat( "attack_range", "500" ); + idVec3 testPoint; + idVec3 finalPoint = vec3_zero; + bool clipped = false; + float yaw = (GetOrigin() - enemy->GetOrigin()).ToYaw(); + int num, i, j; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idBounds bounds; + + //test 8 points around the enemy, starting with one directly in front of it + idList listo; + for ( i=0;i<8;i++ ) { + testPoint = enemy->GetOrigin() + distance * idAngles( 0, yaw, 0 ).ToForward(); + testPoint.z += spawnArgs.GetFloat( "attack_z", "300" ); + yaw += 45; + if ( yaw > 360 ) { + yaw -= 360; + } + if ( yaw == 180 ) { + continue; + } + + //make sure it wont clip into anything at testPoint + clipped = false; + bounds.FromTransformedBounds( GetPhysics()->GetBounds(), testPoint, GetPhysics()->GetAxis() ); + num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( j = 0; j < num; j++ ) { + cm = clipModels[ j ]; + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + idEntity *hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + if ( physicsObj.ClipContents( cm ) ) { + clipped = true; + break; + } + } + if ( clipped ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorRed, bounds, vec3_origin, 10000 ); + } + continue; + } else { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugBounds( colorGreen, bounds, vec3_origin, 10000 ); + } + } + + //make sure we can path there + if ( !PointReachableAreaNum( testPoint ) ) { + if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorRed, GetOrigin(), testPoint, 10, 10000 ); + } + continue; + } else if ( ai_debugBrain.GetBool() ) { + gameRenderWorld->DebugArrow( colorGreen, GetOrigin(), testPoint, 10, 10000 ); + } + + listo.Append( testPoint ); + } + + if ( listo.Num() ) { + finalPoint = listo[gameLocal.random.RandomInt(listo.Num())]; + } + if ( finalPoint != vec3_zero ) { + idThread::ReturnVector( finalPoint ); + return; + } else { + idThread::ReturnVector( vec3_zero ); + return; + } +} + +void hhMonsterAI::Event_SoundOnModel() { + soundOnModel = !soundOnModel; +} + +void hhMonsterAI::Event_ActivatePhysics() { + ActivatePhysics( this ); +} + +void hhMonsterAI::Event_IsVehicleDocked() { + if ( GetVehicleInterfaceLocal()->IsVehicleDocked() ) { + idThread::ReturnInt( true ); + return; + } + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_EnemyInSpirit() { + if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( enemy.GetEntity() ); + if ( player && player->IsSpiritWalking() ) { + idThread::ReturnInt( true ); + return; + } + } + + idThread::ReturnInt( false ); +} + +void hhMonsterAI::Event_SetNeverDormant( int enable ) { + if ( head.IsValid() ) { + head->fl.neverDormant = ( enable != 0 ); + } + fl.neverDormant = ( enable != 0 ); + dormantStart = 0; +} + diff --git a/neo/prey/Prey/game_mountedgun.cpp b/neo/prey/Prey/game_mountedgun.cpp new file mode 100644 index 00000000..f1d4d995 --- /dev/null +++ b/neo/prey/Prey/game_mountedgun.cpp @@ -0,0 +1,1048 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_ScanForClosestTarget( "" ); +const idEventDef EV_StartAttack( "" ); +const idEventDef EV_NextCycleAnim( "", "ds" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhMountedGun ) + EVENT( EV_Activate, hhMountedGun::Event_Activate ) + EVENT( EV_Deactivate, hhMountedGun::Event_Deactivate ) + EVENT( EV_PostSpawn, hhMountedGun::Event_PostSpawn ) + EVENT( EV_ScanForClosestTarget, hhMountedGun::Event_ScanForClosestTarget ) + EVENT( EV_StartAttack, hhMountedGun::Event_StartAttack ) + + EVENT( EV_NextCycleAnim, hhMountedGun::Event_NextCycleAnim ) +END_CLASS + +/* +================ +hhMountedGun::hhMountedGun +================ +*/ +hhMountedGun::hhMountedGun() { + yawVelocity = 0.0f; +} + +/* +================ +hhMountedGun::Spawn +================ +*/ +void hhMountedGun::Spawn( void ) { + targetingLaser = NULL; + boneHub.Clear(); + boneGun.Clear(); + boneOrigin.Clear(); + + fl.takedamage = false; // Gun doesn't take damage unless it is active + + deathwalkDelay = SEC2MS( spawnArgs.GetInt( "deathwalk_delay", "1" ) ); + burstRateOfFire = SEC2MS( spawnArgs.GetFloat("burstRateOfFire") ); + shotsPerBurst = spawnArgs.GetInt("shotsPerBurst"); + shotsPerBurstCounter = 0; + + firingRange = spawnArgs.GetFloat( "firingRange" ); + nextFireTime = 0; + projectileDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_projectile" ) ); + if ( !projectileDict ) { + gameLocal.Error( "Unknown def_projectile: %s\n", spawnArgs.GetString( "def_projectile" ) ); + } + + // Set up muzzle flash light + memset( &muzzleFlash, 0, sizeof(renderLight_t) ); + muzzleFlash.lightId = 500 + entityNumber; + muzzleFlash.shader = declManager->FindMaterial( spawnArgs.GetString("mtr_flashShader"), false ); + muzzleFlashHandle = -1; + muzzleFlash.pointLight = true; + muzzleFlashEnd = gameLocal.GetTime(); + muzzleFlash.lightRadius = spawnArgs.GetVector( "flashSize" ); + muzzleFlashDuration = SEC2MS( spawnArgs.GetFloat("flashTime") ); + idVec3 flashColor = spawnArgs.GetVector( "flashColor" ); + muzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + SpawnParts(); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( GetPhysics()->GetContents() ); + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + SetPhysics( &physicsObj ); + + prevIdealLookAngle.Zero(); + + pitchController.Clear(); + pitchController.Setup( this, spawnArgs.GetString("bone_Hub"), idAngles(-90, 0, 0), idAngles(90, 0, 0), idAngles(90, 0, 0), idAngles(1,0,0) ); + + channelBody = ChannelName2Num( "body" ); + channelBarrel = ChannelName2Num( "barrel" ); + + PlayCycle( channelBody, "idle", 0 ); + + PVSArea = gameLocal.pvs.GetPVSArea( GetOrigin() ); + + PostEventMS( &EV_PostSpawn, 0 ); + + gunMode = GM_DORMANT; + team = spawnArgs.GetInt( "team", "1" ); + + PlayAnim( ANIMCHANNEL_ALL, "idle_close", 0 ); +} + +void hhMountedGun::Save(idSaveGame *savefile) const { + savefile->WriteInt( team ); + savefile->WriteInt( gunMode ); + pitchController.Save( savefile ); + trackMover.Save( savefile ); + targetingLaser.Save( savefile ); + boneHub.Save( savefile ); + boneGun.Save( savefile ); + boneExhaust.Save( savefile ); + boneOrigin.Save( savefile ); + enemy.Save( savefile ); + savefile->WriteFloat( enemyRange ); + savefile->WriteFloat( firingRange ); + savefile->WriteInt( nextFireTime ); + savefile->WriteFloat( burstRateOfFire ); + savefile->WriteInt( shotsPerBurst ); + savefile->WriteInt( shotsPerBurstCounter ); + savefile->WriteRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( muzzleFlashHandle ); + savefile->WriteInt( muzzleFlashEnd ); + savefile->WriteInt( muzzleFlashDuration ); + savefile->WriteAngles( idealLookAngle ); + savefile->WriteAngles( prevIdealLookAngle ); + savefile->WriteStaticObject( physicsObj ); + savefile->WriteInt( animDoneTime ); + savefile->WriteInt( PVSArea ); + savefile->WriteInt( channelBody ); + savefile->WriteInt( channelBarrel ); + savefile->WriteFloat( yawVelocity ); + savefile->WriteInt( deathwalkDelay ); +} + +void hhMountedGun::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( team ); + savefile->ReadInt( reinterpret_cast ( gunMode ) ); + pitchController.Restore( savefile ); + trackMover.Restore( savefile ); + targetingLaser.Restore( savefile ); + boneHub.Restore( savefile ); + boneGun.Restore( savefile ); + boneExhaust.Restore( savefile ); + boneOrigin.Restore( savefile ); + enemy.Restore( savefile ); + savefile->ReadFloat( enemyRange ); + savefile->ReadFloat( firingRange ); + savefile->ReadInt( nextFireTime ); + savefile->ReadFloat( burstRateOfFire ); + savefile->ReadInt( shotsPerBurst ); + savefile->ReadInt( shotsPerBurstCounter ); + savefile->ReadRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( muzzleFlashHandle ); + savefile->ReadInt( muzzleFlashEnd ); + savefile->ReadInt( muzzleFlashDuration ); + savefile->ReadAngles( idealLookAngle ); + savefile->ReadAngles( prevIdealLookAngle ); + savefile->ReadStaticObject( physicsObj ); + savefile->ReadInt( animDoneTime ); + savefile->ReadInt( PVSArea ); + savefile->ReadInt( channelBody ); + savefile->ReadInt( channelBarrel ); + savefile->ReadFloat( yawVelocity ); + savefile->ReadInt( deathwalkDelay ); + + projectileDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_projectile" ) ); + if ( !projectileDict ) { + gameLocal.Error( "Unknown def_projectile: %s\n", spawnArgs.GetString( "def_projectile" ) ); + } + RestorePhysics( &physicsObj ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + muzzleFlashHandle = -1; +} + +/* +================ +hhMountedGun::~hhMountedGun +================ +*/ +hhMountedGun::~hhMountedGun( void ) { + SAFE_REMOVE( targetingLaser ); + + SAFE_FREELIGHT( muzzleFlashHandle ); + + trackMover = NULL; +} + +/* +================ +hhMountedGun::SpawnParts +================ +*/ +void hhMountedGun::SpawnParts() { + boneHub.name = spawnArgs.GetString("bone_Hub"); + boneHub.handle = GetAnimator()->GetJointHandle( boneHub.name.c_str() ); + + boneGun.name = spawnArgs.GetString("bone_Gun"); + boneGun.handle = GetAnimator()->GetJointHandle( boneGun.name.c_str() ); + + boneOrigin.name = spawnArgs.GetString("bone_Origin"); + boneOrigin.handle = GetAnimator()->GetJointHandle( boneOrigin.name.c_str() ); + + UpdateBoneInfo(); + + targetingLaser = hhBeamSystem::SpawnBeam( boneGun.origin, spawnArgs.GetString("beam") ); + if( targetingLaser.IsValid() ) { + targetingLaser->MoveToJoint( this, boneGun.name.c_str() ); + targetingLaser->BindToJoint( this, boneGun.name.c_str(), false ); + targetingLaser->Activate( false ); + } +} + +/* +===================== +hhMountedGun::Hide +===================== +*/ +void hhMountedGun::Hide() { + hhAnimatedEntity::Hide(); + + if( targetingLaser.IsValid() ) { + targetingLaser->Hide(); + } +} + +/* +===================== +hhMountedGun::Show +===================== +*/ +void hhMountedGun::Show() { + hhAnimatedEntity::Show(); + + if( targetingLaser.IsValid() ) { + targetingLaser->Show(); + } +} + +/* +===================== +hhMountedGun::DetermineTargetingLaserEndPoint +===================== +*/ +idVec3 hhMountedGun::DetermineTargetingLaserEndPoint() { + trace_t traceInfo; + idVec3 start = boneGun.origin; + idVec3 dist = boneGun.axis[0] * CM_MAX_TRACE_DIST; + + gameLocal.clip.TracePoint( traceInfo, start, start + dist, MASK_SHOT_BOUNDINGBOX, this ); + + return (enemy.IsValid()) ? traceInfo.endpos + enemy->GetAxis()[2] * -10.0f : traceInfo.endpos; +} + +/* +===================== +hhMountedGun::Ticker +===================== +*/ +void hhMountedGun::Ticker( void ) { + if( !pitchController.IsFinishedMoving(PITCH) ) { + pitchController.Update( gameLocal.GetTime() ); + } + + UpdateBoneInfo(); + + if( targetingLaser.IsValid() && !targetingLaser->IsHidden() ) { + targetingLaser->SetTargetLocation( DetermineTargetingLaserEndPoint() ); + } + + UpdateMuzzleFlash(); +} + +/* +================ +hhMountedGun::UpdateOrientation +================ +*/ +void hhMountedGun::UpdateOrientation() { + SetAngles( idAngles( 0, idealLookAngle.yaw, 0 ) ); + pitchController.TurnTo( idealLookAngle ); +} + +/* +================ +hhMountedGun::UpdateBoneInfo +================ +*/ +void hhMountedGun::UpdateBoneInfo() { + idVec3 TempVec; + + GetJointWorldTransform( boneHub.handle, boneHub.origin, boneHub.axis ); + GetJointWorldTransform( boneGun.handle, boneGun.origin, boneGun.axis ); + + GetJointWorldTransform( boneOrigin.handle, boneOrigin.origin, boneOrigin.axis ); +} + +/* +================ +hhMountedGun::SetIdealLookAngle +================ +*/ +void hhMountedGun::SetIdealLookAngle( const idAngles& LookAngle ) { + idealLookAngle.pitch = -hhMath::AngleNormalize180( LookAngle.pitch ); + idealLookAngle.yaw = hhMath::AngleNormalize360( LookAngle.yaw ); + idealLookAngle.roll = 0.0f; + + if( prevIdealLookAngle == idealLookAngle ) { + return; + } + + prevIdealLookAngle = idealLookAngle; + + UpdateOrientation(); +} + +/* +================ +hhMountedGun::AttemptToRemoveMuzzleFlash +================ +*/ +void hhMountedGun::AttemptToRemoveMuzzleFlash() { + if( gameLocal.GetTime() >= muzzleFlashEnd ) { + SAFE_FREELIGHT( muzzleFlashHandle ); + } +} + +/* +================ +hhMountedGun::UpdateMuzzleFlash +================ +*/ +void hhMountedGun::UpdateMuzzleFlash() { + AttemptToRemoveMuzzleFlash(); + + if( muzzleFlashHandle != -1 ) { + UpdateMuzzleFlashPosition(); + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } +} + +/* +================ +hhMountedGun::UpdateMuzzleFlashPosition +================ +*/ +void hhMountedGun::UpdateMuzzleFlashPosition() { + muzzleFlash.axis = boneGun.axis; + muzzleFlash.origin = boneGun.origin; + + //TEST + trace_t trace; + idVec3 flashSize = spawnArgs.GetVector( "flashSize" ); + if( gameLocal.clip.TracePoint(trace, muzzleFlash.origin, muzzleFlash.origin + muzzleFlash.axis[0] * flashSize[0], MASK_SHOT_BOUNDINGBOX, this) ) { + flashSize[0] *= trace.fraction; + } + muzzleFlash.lightRadius = flashSize; + muzzleFlash.origin += muzzleFlash.axis[0] * flashSize[0] * 0.5f; + muzzleFlash.lightCenter.Set( flashSize[0] * -0.45f, 0.0f, 0.0f ); + + hhUtils::Swap( muzzleFlash.lightRadius[0], muzzleFlash.lightRadius[2] ); + hhUtils::Swap( muzzleFlash.lightCenter[0], muzzleFlash.lightCenter[2] ); + muzzleFlash.axis = hhUtils::SwapXZ( muzzleFlash.axis ); + //TEST +} + +/* +================ +hhMountedGun::MuzzleFlash +================ +*/ +void hhMountedGun::MuzzleFlash() { + if( !muzzleFlash.lightRadius[0] ) { + return; + } + + UpdateMuzzleFlashPosition(); + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.GetTime() ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.RandomFloat(); + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.GetTime() + muzzleFlashDuration; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } +} + +/* +================ +hhMountedGun::TraceTargetVerified +================ +*/ +bool hhMountedGun::TraceTargetVerified( const idActor* target, int traceEntNum ) const { + int entNum = target->InVehicle() ? target->GetVehicleInterface()->GetVehicle()->entityNumber : target->entityNumber; + return entNum == traceEntNum; +} + +/* +================ +hhMountedGun::ScanForClosestTarget +================ +*/ +idActor *hhMountedGun::ScanForClosestTarget() { + trace_t TraceInfo; + idEntity *entity; + idActor *actor; + hhPlayer *player; + float firingRangeSquared = firingRange * firingRange; + float closestDistSqr = firingRangeSquared; + idActor *closestActor = NULL; + pvsHandle_t pvs; + + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + + for ( entity = gameLocal.activeEntities.Next(); entity != NULL; entity = entity->activeNode.Next() ) { + if ( entity->fl.hidden || entity->fl.isDormant || !entity->IsType( idActor::Type ) ) { + continue; + } + + actor = static_cast( entity ); + + // Check if this actor is in the PVS (needed?) + if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { + continue; + } + + // Check if the actor can be damaged and should be targeted by the gun + // Also, check the actor's team, only player team (humans, player) should be targetted + if ( !actor->fl.takedamage || actor->GetHealth() <= 0 || actor->team != 0 ) { + if ( !actor->InVehicle() ) { //HUMANHEAD jsh target vehicles + continue; + } + } + + // Player-specific checks -- ignore the spirit player + if ( actor->IsType( hhPlayer::Type ) ) { + player = static_cast( actor ); + if ( !player || player->IsSpiritOrDeathwalking() ) { + continue; + } + } + + // Calculate the distance to the actor + float distSqr = (actor->GetEyePosition() - GetOrigin()).LengthSqr(); + + // Ignore actors beyond the max distance + if( distSqr > firingRangeSquared ) { + continue; + } + + // Only check if this distance is closer than the others + if( distSqr > closestDistSqr ) { + continue; + } + + // Check if this actor is visible from the gun + if( !gameLocal.clip.TracePoint(TraceInfo, GetOrigin(), actor->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, this) ) { + continue; + } + + if( !TraceTargetVerified(actor, TraceInfo.c.entityNum) ) { + continue; + } + + if ( actor->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(actor); + if ( player && player->GetLastResurrectTime() ) { + if ( gameLocal.time < player->GetLastResurrectTime() + deathwalkDelay ) { + continue; + } + } + } + + closestActor = actor; + closestDistSqr = distSqr; + } + + gameLocal.pvs.FreeCurrentPVS( pvs ); + + return closestActor; +} + +/* +================ +hhMountedGun::PlayAnim +================ +*/ +void hhMountedGun::PlayAnim( int iChannelNum, const char* pAnim, int iBlendTime ) { + PlayAnim( iChannelNum, GetAnimator()->GetAnim( pAnim ), iBlendTime ); +} + +/* +================ +hhMountedGun::PlayAnim +================ +*/ +void hhMountedGun::PlayAnim( int iChannelNum, int pAnim, int iBlendTime ) { + ClearAnims( iChannelNum, iBlendTime ); + + GetAnimator()->PlayAnim( iChannelNum, pAnim, gameLocal.GetTime(), iBlendTime ); + animDoneTime = GetAnimator()->CurrentAnim( iChannelNum )->Length(); + animDoneTime = (animDoneTime > iBlendTime) ? animDoneTime - iBlendTime : animDoneTime; + + animDoneTime += gameLocal.GetTime(); +} + +/* +================ +hhMountedGun::PlayCycle +================ +*/ +void hhMountedGun::PlayCycle( int iChannelNum, const char* pAnim, int iBlendTime ) { + PlayCycle( iChannelNum, GetAnimator()->GetAnim( pAnim ), iBlendTime ); +} + +/* +================ +hhMountedGun::NextCycleAnim + +// Cycles the next animation in after the current one completes +================ +*/ +void hhMountedGun::NextCycleAnim( int iChannelNum, const char* pAnim ) { + PostEventMS( &EV_NextCycleAnim, GetAnimDoneTime() - gameLocal.GetTime(), iChannelNum, pAnim ); +} + +void hhMountedGun::Event_NextCycleAnim( int iChannelNum, const char* pAnim ) { + PlayCycle( iChannelNum, pAnim, 0 ); +} + +/* +================ +hhMountedGun::PlayCycle +================ +*/ +void hhMountedGun::PlayCycle( int iChannelNum, int pAnim, int iBlendTime ) { + ClearAnims( iChannelNum, iBlendTime ); + + GetAnimator()->CycleAnim( iChannelNum, pAnim, gameLocal.GetTime(), iBlendTime ); +} + +/* +================ +hhMountedGun::ClearAnims +================ +*/ +void hhMountedGun::ClearAnims( int iChannelNum, int iBlendTime ) { + GetAnimator()->Clear( iChannelNum, gameLocal.GetTime(), iBlendTime ); + + animDoneTime = 0; +} + +/* +===================== +hhMountedGun::Event_PostSpawn +===================== +*/ +void hhMountedGun::Event_PostSpawn() { + // Find the trackmover if it exists + trackMover = gameLocal.FindEntity( spawnArgs.GetString("target") ); + if( trackMover.IsValid() && trackMover->IsType( hhTrackMover::Type ) ) { //HUMANHEAD PCF mdl 04/26/06 - Only bind to track movers, in case target is only for triggers + SetOrigin( trackMover->GetOrigin() ); + Bind( trackMover.GetEntity(), false ); + } + + SetIdealLookAngle( GetAxis().ToAngles() ); +} + + +/* +===================== +hhMountedGun::Event_Activate +===================== +*/ +void hhMountedGun::Event_Activate( idEntity* pActivator ) { + if ( gunMode == GM_DORMANT ) { + gunMode = GM_IDLE; + + BecomeActive( TH_THINK | TH_TICKER ); + + ProcessEvent( &EV_ScanForClosestTarget ); + } +} + +/* +===================== +hhMountedGun::Awaken +===================== +*/ + +void hhMountedGun::Awaken() { + if ( gunMode != GM_DEAD ) { + if( targetingLaser.IsValid() && !targetingLaser->IsActivated() ) { + targetingLaser->Activate( true ); + } + + PreAttack(); + fl.takedamage = true; // Gun can now be shot + + PlayAnim( ANIMCHANNEL_ALL, "open", 0 ); + NextCycleAnim( ANIMCHANNEL_ALL, "idle_open" ); + } +} + +/* +===================== +hhMountedGun::Event_Deactivate +===================== +*/ +void hhMountedGun::Event_Deactivate() { + if (gunMode != GM_DEAD) { + if( targetingLaser.IsValid() && targetingLaser->IsActivated() ) { + targetingLaser->Activate( false ); + } + + if( trackMover.IsValid() ) { + trackMover->ProcessEvent( &EV_Deactivate ); + } + + CancelEvents( &EV_ScanForClosestTarget ); + CancelEvents( &EV_StartAttack ); + + gunMode = GM_DORMANT; + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + PlayAnim( ANIMCHANNEL_ALL, "close", 0 ); + NextCycleAnim( ANIMCHANNEL_ALL, "idle_close" ); + } +} + +/* +===================== +hhMountedGun::Sleep + +// Very similar to dormant, except the gun continues to think and look for enemies while closed +===================== +*/ +void hhMountedGun::Sleep() { + if( targetingLaser.IsValid() && targetingLaser->IsActivated() ) { + targetingLaser->Activate( false ); + targetingLaser->SetShaderParm( SHADERPARM_MODE, 0 ); + } + + // Note: A sleeping gun doesn't disable the track mover + CancelEvents( &EV_StartAttack ); + + gunMode = GM_IDLE; + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + PlayAnim( ANIMCHANNEL_ALL, "close", 0 ); + NextCycleAnim( ANIMCHANNEL_ALL, "idle_close" ); +} + +//=============================================================================================================== +//=============================================================================================================== +//=============================================================================================================== +//=============================================================================================================== +//=============================================================================================================== + +void hhMountedGun::Think() { + hhAnimatedEntity::Think(); + + if (thinkFlags & TH_THINK) { + // Run the current state + switch ( gunMode ) { + case GM_DORMANT: + case GM_IDLE: + case GM_DEAD: + break; + case GM_PREATTACKING: + TurnTowardsEnemy( DEG2RAD( spawnArgs.GetFloat( "turnMax", "3" ) ), DEG2RAD( spawnArgs.GetFloat( "turnAccel", "0.2" ) ) ); + break; + case GM_ATTACKING: + TurnTowardsEnemy( DEG2RAD( spawnArgs.GetFloat( "attackTurnMax", "0.5" ) ), DEG2RAD( spawnArgs.GetFloat( "attackTurnAccel", "0.05" ) ) ); + Attack(); + break; + case GM_RELOADING: + // TODO: Handled by an event + if ( ReadyToFire() ) { // hack! + PreAttack(); + } + break; + } + } +} + +bool hhMountedGun::GetFacePosAngle( const idVec3 &sourceOrigin, float angle1, const idVec3 &targetOrigin, float &delta ) { + float diff; + float angle2; + + angle2 = hhUtils::PointToAngle( targetOrigin.x - sourceOrigin.x, targetOrigin.y - sourceOrigin.y ); + if(angle2 > angle1) { + diff = angle2 - angle1; + + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return false; + } + else { + delta = diff; + return true; + } + } + else { + diff = angle1 - angle2; + if( diff > DEG2RAD(180.0f) ) { + delta = DEG2RAD(359.9f) - diff; + return true; + } + else { + delta = diff; + return false; + } + } +} + +void hhMountedGun::TurnTowardsEnemy( float maxYawVelocity, float yawAccel ) { + idVec3 dirToEnemy; + idVec3 localDirToEnemy; + + bool dir; + float deltaYaw; + + if( !EnemyIsVisible() ) { // Enemy isn't valid or visible, so go to sleep until the enemy is available again + return; + } + + if ( !enemy->IsType( hhPlayer::Type ) ) { // Turn twice as fast if the enemy is not a player (to guarantee that the gun will kill other humans/creatures) + maxYawVelocity *= 2.0f; + yawAccel *= 2.0f; + } + + idVec3 currentVec = this->GetAxis()[0]; + float currentYaw = DEG2RAD( currentVec.ToYaw() ); + + // Face toward the enemy + dir = GetFacePosAngle( GetOrigin(), currentYaw, enemy->GetOrigin(), deltaYaw ); + + // Acceleration + if ( dir ) { + yawVelocity += yawAccel; + } else { + yawVelocity -= yawAccel; + } + + if ( yawVelocity > maxYawVelocity ) { + yawVelocity = maxYawVelocity; + } else if ( yawVelocity < -maxYawVelocity ) { + yawVelocity = -maxYawVelocity; + } + + deltaYaw = yawVelocity; + + // Compute the pitch to the enemy + dirToEnemy = ( ( enemy->GetEyePosition() + enemy->GetOrigin() ) * 0.5f ) - boneGun.origin; + dirToEnemy.Normalize(); + GetAxis().ProjectVector( dirToEnemy, localDirToEnemy ); + + SetIdealLookAngle( idAngles( localDirToEnemy.ToPitch(), RAD2DEG( currentYaw + deltaYaw ), 0.0f ) ); + + StartSound( "snd_rotate", SND_CHANNEL_BODY3 ); +} + +bool hhMountedGun::EnemyIsInRange() { + idVec3 dirToEnemy; + + if ( !enemy.IsValid() ) { + return false; + } + + dirToEnemy = enemy->GetEyePosition() - boneHub.origin; + if ( dirToEnemy.LengthSqr() > firingRange * firingRange ) { + return false; + } + + return true; +} + +bool hhMountedGun::EnemyCanBeAttacked() { + if ( !enemy.IsValid() ) { // No enemy + return false; + } + + // Check if the enemy is visible and is alive + if ( ClearLineOfFire() && enemy->health > 0 ) { + return true; + } + + return false; +} + +/* +===================== +hhMountedGun::Event_ScanForClosestTarget +===================== +*/ +void hhMountedGun::Event_ScanForClosestTarget() { + idActor *entity = ScanForClosestTarget(); + + enemy = entity; + if ( entity && gunMode == GM_IDLE ) { // Sleeping and found a target + Awaken(); + } + else if ( !entity && gunMode != GM_IDLE ) { // No target, so go to sleep if not already sleeping + Sleep(); + } + + PostEventSec( &EV_ScanForClosestTarget, spawnArgs.GetFloat("scanPeriod") ); +} + +void hhMountedGun::PreAttack() { + gunMode = GM_PREATTACKING; + + // Change the beam to something more threatening + targetingLaser->SetShaderParm( SHADERPARM_MODE, 1 ); + + // Play a preattack warning sound + StartSound( "snd_preattack", SND_CHANNEL_ANY ); + + // Delay before attacking + PostEventSec( &EV_StartAttack, spawnArgs.GetFloat( "attackDelay", "0.2" ) ); // Delay a bit before attacking +} + +void hhMountedGun::Event_StartAttack() { + gunMode = GM_ATTACKING; +} + +void hhMountedGun::Attack() { + hhPlayer *player = NULL; + + if ( !enemy.IsValid() ) { + return; + } + + if ( enemy->IsType( hhPlayer::Type ) ) { + player = static_cast( enemy.GetEntity() ); + if ( player->IsSpiritOrDeathwalking() ) { + return; + } + } + + // Actually fire the gun + if ( ReadyToFire() ) { + + Fire(); + + shotsPerBurstCounter++; + if ( shotsPerBurstCounter >= shotsPerBurst || enemy->GetHealth() <= 0 ) { + Reload(); + } + } +} + +void hhMountedGun::Reload() { + gunMode = GM_RELOADING; + PlayAnim( ANIMCHANNEL_ALL, "reload", 0 ); + + nextFireTime = gameLocal.GetTime() + SEC2MS( 2.0f ); // HACK: fake way of currently forcing a wait when reloading + shotsPerBurstCounter = 0; + + targetingLaser->SetShaderParm( SHADERPARM_MODE, 0 ); + + if ( enemy->GetHealth() <= 0 ) { // Enemy was killed, so clear it and wait for the gun to find a new enemy + enemy.Clear(); + } + + StopSound( SND_CHANNEL_BODY3 ); // Stop the rotation sound +} + +/* +===================== +hhMountedGun::DetermineNextFireTime +===================== +*/ +int hhMountedGun::DetermineNextFireTime() { + return gameLocal.GetTime() + burstRateOfFire; +} + +/* +===================== +hhMountedGun::ReadyToFire +===================== +*/ +bool hhMountedGun::ReadyToFire() { + return gameLocal.GetTime() > nextFireTime; +} + +/* +================ +hhMountedGun::Fire +================ +*/ +void hhMountedGun::Fire() { + idVec3 LaunchDir; + idMat3 LaunchAxis; + + PlayAnim( channelBarrel, "fireA", 0 ); + nextFireTime = DetermineNextFireTime(); + + MuzzleFlash(); + + hhProjectile* pProjectile = hhProjectile::SpawnProjectile( projectileDict ); + if( !pProjectile ) { + return; + } + + LaunchAxis = hhUtils::RandomSpreadDir( boneGun.axis, DEG2RAD(spawnArgs.GetFloat("spread"))).ToMat3(); + pProjectile->Create( this, boneGun.origin, LaunchAxis ); + pProjectile->Launch( boneGun.origin, LaunchAxis, vec3_zero ); +} + +/* +===================== +hhMountedGun::EnemyIsVisible + +// If the enemy is visible at any angle from the gun +===================== +*/ +bool hhMountedGun::EnemyIsVisible() { + trace_t traceInfo; + + if( !enemy.IsValid() || !EnemyIsInPVS() ) { + return false; + } + + if( !gameLocal.clip.TracePoint( traceInfo, GetOrigin(), enemy->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, this ) ) { + return true; + } + + if( TraceTargetVerified( enemy.GetEntity(), traceInfo.c.entityNum ) ) { + return true; + } + + return false; +} + +/* +===================== +hhMountedGun::ClearLineOfFire + +// If the enemy can be shot (line of sight from the gun barrel to the enemy) +===================== +*/ +bool hhMountedGun::ClearLineOfFire() { + trace_t TraceInfo; + idVec3 BonePos; + idMat3 BoneAxis; + + if( !enemy.IsValid() || !EnemyIsInPVS() ) { + return false; + } + + if( !gameLocal.clip.TracePoint(TraceInfo, boneGun.origin, boneGun.origin + boneGun.axis[0] * firingRange, MASK_SHOT_BOUNDINGBOX, this) ) { + return false; + } + + if( !TraceTargetVerified(enemy.GetEntity(), TraceInfo.c.entityNum) ) { + return false; + } + + return true; +} + +/* +===================== +hhMountedGun::EnemyIsInPVS +===================== +*/ +bool hhMountedGun::EnemyIsInPVS() { + pvsHandle_t PVSHandle = gameLocal.pvs.SetupCurrentPVS( PVSArea ); + + bool bResult = gameLocal.pvs.InCurrentPVS( PVSHandle, enemy->GetPVSAreas(), enemy->GetNumPVSAreas() ); + + gameLocal.pvs.FreeCurrentPVS( PVSHandle ); + + return bResult; +} + +/* +================ +hhMountedGun::Killed +================ +*/ +void hhMountedGun::Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ) { + hhFxInfo fxInfo; + + if ( gunMode != GM_DEAD ) { + BecomeInactive( TH_THINK|TH_TICKER ); + CancelEvents( &EV_ScanForClosestTarget ); + CancelEvents( &EV_StartAttack ); + CancelEvents( &EV_NextCycleAnim ); + + fxInfo.SetNormal( -GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_detonate"), boneHub.origin, GetAxis(), &fxInfo ); + + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), this); + + if( targetingLaser.IsValid() ) { + targetingLaser->Activate( false ); + } + SAFE_REMOVE( targetingLaser ); + SAFE_FREELIGHT( muzzleFlashHandle ); + + if( trackMover.IsValid() ) { + trackMover->ProcessEvent( &EV_Deactivate ); + } + + const char *killedModel = spawnArgs.GetString("model_killed", NULL); + if (killedModel) { + SetModel(killedModel); + UpdateVisuals(); + GetPhysics()->SetContents(0); + fl.takedamage = false; + } + + RemoveBinds(); + + StopSound( SND_CHANNEL_ANY, false ); + + StartSound( "snd_die", SND_CHANNEL_ANY); + + ActivateTargets( pAttacker ); + + gunMode = GM_DEAD; + } +} + +void hhMountedGun::jointInfo_t::Save( idSaveGame *savefile ) const { + savefile->WriteString( name ); + savefile->WriteJoint( handle ); + savefile->WriteVec3( origin ); + savefile->WriteMat3( axis ); +} + +void hhMountedGun::jointInfo_t::Restore( idRestoreGame *savefile ) { + savefile->ReadString( name ); + savefile->ReadJoint( handle ); + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); +} + diff --git a/neo/prey/Prey/game_mountedgun.h b/neo/prey/Prey/game_mountedgun.h new file mode 100644 index 00000000..db937005 --- /dev/null +++ b/neo/prey/Prey/game_mountedgun.h @@ -0,0 +1,158 @@ +#ifndef __HH_MOUNTEDGUN_H +#define __HH_MOUNTEDGUN_H + +typedef enum { + GM_DORMANT, + GM_IDLE, + GM_PREATTACKING, + GM_ATTACKING, + GM_RELOADING, + GM_DEAD +} gunMode_t; + +/********************************************************************** + +hhMountedGun + +**********************************************************************/ +class hhMountedGun: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhMountedGun ); + +public: + hhMountedGun(); + virtual ~hhMountedGun(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Hide(); + virtual void Show(); + +protected: + void SpawnParts(); + idVec3 DetermineTargetingLaserEndPoint(); + void Ticker(); + + virtual void Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ); + + int DetermineNextFireTime(); + bool ReadyToFire(); + void Fire(); + + + void MuzzleFlash(); + void UpdateMuzzleFlash(); + void AttemptToRemoveMuzzleFlash(); + void UpdateMuzzleFlashPosition(); + + void UpdateOrientation(); + void UpdateBoneInfo(); + + bool GetFacePosAngle( const idVec3 &sourceOrigin, float angle1, const idVec3 &targetOrigin, float &delta ); + void SetIdealLookAngle( const idAngles& LookAngle ); + + idActor *ScanForClosestTarget(); + + bool TraceTargetVerified( const idActor* target, int traceEntNum ) const; + +protected: + bool ClearLineOfFire(); + bool EnemyIsVisible(); + bool EnemyIsInPVS(); + + void PlayAnim( int iChannelNum, const char* pAnim, int iBlendTime ); + void PlayAnim( int iChannelNum, int pAnim, int iBlendTime ); + void PlayCycle( int iChannelNum, const char* pAnim, int iBlendTime ); + void PlayCycle( int iChannelNum, int pAnim, int iBlendTime ); + void ClearAnims( int iChannelNum, int iBlendTime ); + int GetAnimDoneTime() { return animDoneTime; } + + void NextCycleAnim( int iChannelNum, const char* pAnim ); + void Event_NextCycleAnim( int iChannelNum, const char* pAnim ); + +protected: + void Awaken(); + + void Sleep(); + + void Event_PostSpawn(); + void Event_Activate( idEntity* pActivator ); + void Event_Deactivate(); + void Event_StopRotating(); + + void Event_ScanForClosestTarget(); + void Event_StartAttack(); + +protected: + // CJR STUFF + int team; + gunMode_t gunMode; + void Think(); + void TurnTowardsEnemy( float maxYawVelocity, float yawAccel ); + + bool EnemyCanBeAttacked(); + bool EnemyIsInRange(); + void PreAttack(); + void Attack(); + void Reload(); + +protected: + hhBoneController pitchController; + + const idDict* projectileDict; + + idEntityPtr trackMover; + + idEntityPtr targetingLaser; + + struct jointInfo_t { + idStr name; + jointHandle_t handle; + idVec3 origin; + idMat3 axis; + + jointInfo_t() { Clear(); } + void Clear() { handle = INVALID_JOINT; origin.Zero(); axis = mat3_identity; } + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + }; + + jointInfo_t boneHub; + jointInfo_t boneGun; + jointInfo_t boneExhaust; + jointInfo_t boneOrigin; + + idEntityPtr enemy; + float enemyRange; + + float firingRange; + + int nextFireTime; + float burstRateOfFire; + int shotsPerBurst; + int shotsPerBurstCounter; + + renderLight_t muzzleFlash; + int muzzleFlashHandle; + int muzzleFlashEnd; + int muzzleFlashDuration; + + idAngles idealLookAngle; + idAngles prevIdealLookAngle; + + idPhysics_Parametric physicsObj; + + int animDoneTime; + + int PVSArea; + + // nla - Other vars + int channelBody; + int channelBarrel; + int deathwalkDelay; //time to wait in msecs after killing something + + // cjr vars + float yawVelocity; +}; + +#endif diff --git a/neo/prey/Prey/game_moveable.cpp b/neo/prey/Prey/game_moveable.cpp new file mode 100644 index 00000000..ff5e336e --- /dev/null +++ b/neo/prey/Prey/game_moveable.cpp @@ -0,0 +1,492 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define PLAYER_COLLISION_PRINTF(e) if( (e)->IsType(hhPlayer::Type) ) gameLocal.Printf + +const idEventDef EV_HoverTo( "hoverTo", "v" ); +const idEventDef EV_HoverMove( "" ); +const idEventDef EV_Unhover( "unhover" ); +const idEventDef EV_FadeOutDebris( "", "f" ); + +CLASS_DECLARATION( idMoveable, hhMoveable ) + EVENT( EV_HoverTo, hhMoveable::Event_HoverTo ) + EVENT( EV_HoverMove, hhMoveable::Event_HoverMove ) + EVENT( EV_Unhover, hhMoveable::Event_Unhover ) + EVENT( EV_Touch, hhMoveable::Event_Touch ) + EVENT( EV_SpawnFxFlyLocal, hhMoveable::Event_SpawnFxFlyLocal ) + EVENT( EV_FadeOutDebris, hhMoveable::Event_StartFadingOut ) +END_CLASS + +/* +================ +hhMoveable::hhMoveable +================ +*/ +hhMoveable::hhMoveable() { +} + +hhMoveable::~hhMoveable() { + SAFE_REMOVE( fxFly ); +} + +/* +================ +hhMoveable::Spawn +================ +*/ +void hhMoveable::Spawn() { + + fl.takedamage = health > 0; + + hoverController = NULL; + nextDamageTime = 0; + + // idMoveable forces friction to (0.6f, 0.6f, friction) + float linearFriction = spawnArgs.GetFloat( "linearFriction", "0.6" ); + float angularFriction = spawnArgs.GetFloat( "angularFriction", "0.6" ); + float contactFriction = spawnArgs.GetFloat( "friction", "0.6" ); + physicsObj.SetFriction( linearFriction, angularFriction, contactFriction ); + + float gravityMagnitude = physicsObj.GetGravity().Length(); + collisionSpeed_min = hhUtils::DetermineFinalFallVelocityMagnitude( spawnArgs.GetFloat("collideDist_min"), gravityMagnitude ); + + currentChannel = SCHANNEL_ANY; + + if (spawnArgs.GetBool("walkthrough")) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); // CJR: Removed CONTENTS_CORPSE because ragdolls were colliding with these moveables + GetPhysics()->SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + } + + // Check if the moveable wants to remove itself after a certain amount of time + float removeTime = spawnArgs.GetFloat("removeTime"); + removeOnCollision = spawnArgs.GetBool("removeOnCollision"); + + float duration = spawnArgs.GetFloat("duration"); // Ignore removeTime if duration is set -mdl + if (duration == 0.0f && !removeOnCollision && removeTime > 0.0f) { + PostEventSec(&EV_Remove, removeTime); + } + + if (!gameLocal.isClient) { + // CJR: spawn an optional flight fx system + const char *flyName = spawnArgs.GetString( "fx_fly" ); + if ( flyName && flyName[0] ) { + BroadcastFx( flyName, EV_SpawnFxFlyLocal ); + } + } + + fadeAlpha.Init(gameLocal.time, 0, 1.0f, 1.0f); + + float fadeTime = spawnArgs.GetFloat("fadeouttime"); + if (duration > 0.0f) { + if (fadeTime > duration) { + fadeTime = duration; + } + if (fadeTime > 0.0f) { + PostEventSec(&EV_FadeOutDebris, duration-fadeTime, fadeTime); + PostEventSec(&EV_Remove, duration); + } + } + + notPushableAI = spawnArgs.GetBool( "notPushableAI", "0" ); +} + +void hhMoveable::Save(idSaveGame *savefile) const { + savefile->WriteFloat( collisionSpeed_min ); + savefile->WriteInt( currentChannel ); + savefile->WriteObject( hoverController ); + savefile->WriteVec3( hoverPosition ); + savefile->WriteVec3( hoverAngle ); + savefile->WriteBool( removeOnCollision ); + savefile->WriteBool( notPushableAI ); + + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); + + fxFly.Save( savefile ); + savefile->WriteInt( nextDamageTime ); +} + +void hhMoveable::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( collisionSpeed_min ); + savefile->ReadInt( currentChannel ); + savefile->ReadObject( reinterpret_cast(hoverController) ); + savefile->ReadVec3( hoverPosition ); + savefile->ReadVec3( hoverAngle ); + savefile->ReadBool( removeOnCollision ); + savefile->ReadBool( notPushableAI ); + + float set; + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); + + fxFly.Restore( savefile ); + savefile->ReadInt( nextDamageTime ); +} + +/* +============ +hhMoveable::SquishedByDoor +============ +*/ +void hhMoveable::SquishedByDoor(idEntity *door) { + // Get rid of any moveables caught in doors + Killed(door, door, 0, vec3_origin, 0); +} + +/* +============ +hhMoveable::Killed +============ +*/ +void hhMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damageAmt, const idVec3 &dir, int location ) { + fl.takedamage = false; + GetPhysics()->SetContents(0); // Turn collision off so other entities can spawn in + + if ( unbindOnDeath ) { + Unbind(); + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets(attacker); + + // nla - Taken from hhAI::Killed + const char *dropDef = spawnArgs.GetString( "def_drop", NULL); + if ( dropDef && *dropDef ) { + idDict args; + args.Set( "origin", physicsObj.GetOrigin().ToString() ); + gameLocal.SpawnObject( dropDef, &args ); + } + + const char *debrisDef = spawnArgs.GetString("def_debrisspawner", NULL); + if ( debrisDef && *debrisDef ) { + hhUtils::SpawnDebrisMass(debrisDef, this); + } + + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +hhMoveable::DetermineNextChannel +================ +*/ +s_channelType hhMoveable::DetermineNextChannel() { + static int NUM_CHANNELS = 6; + + currentChannel = (currentChannel + 1) % NUM_CHANNELS; + return (s_channelType)((currentChannel == SCHANNEL_ANY) ? ++currentChannel : currentChannel); +} + +/* +================ +hhMoveable::AttemptToPlayBounceSound +================ +*/ +void hhMoveable::AttemptToPlayBounceSound( const trace_t &collision, const idVec3 &velocity ) { + static const float minCollisionVelocity = 20.0f; + static const float maxCollisionVelocity = 650.0f; + s_channelType channel; + + float len = velocity * -collision.c.normal; + if( len > minCollisionVelocity && collision.c.material && !(collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + channel = DetermineNextChannel(); + if( StartSound("snd_bounce", channel) ) { + // Change volume only after we know the sound played + float volume = hhUtils::CalculateSoundVolume( len, minCollisionVelocity, maxCollisionVelocity ); + HH_SetSoundVolume( volume, channel ); + } + } +} + +/* +================ +hhMoveable::Collide +================ +*/ +bool hhMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { + idVec3 dir = velocity; + dir.Normalize(); + + if (removeOnCollision) { + StartSound( "snd_splat", SND_CHANNEL_ANY ); + PostEventMS(&EV_Remove, 0); + return true; + } + + AttemptToPlayBounceSound( collision, velocity ); + + idEntity* entity = ValidateEntity( collision.c.entityNum ); + if ( gameLocal.time > nextDamageTime && damage.Length() ) { + if ( entity ) { + if ( DetermineCollisionSpeed(entity, collision.c.point, entity->GetPhysics()->GetLinearVelocity(), GetOrigin(), GetPhysics()->GetLinearVelocity()) > collisionSpeed_min ) { + nextDamageTime = gameLocal.time + SEC2MS(0.5); //prevent multi-collision damage + entity->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, 1.0f, INVALID_JOINT ); + } + } + } + + + if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime ) { + float len = velocity * -collision.c.normal; + if (len > 50) { + hhFxInfo fxInfo; + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo( fxCollide, collision.c.point, mat3_identity, &fxInfo ); + nextCollideFxTime = gameLocal.time + 1000; + } + } + + // CJR: Disable the flight fx effect the first time the moveable hits something + if ( fxFly.IsValid() ) { + fxFly->Nozzle( false ); + SAFE_REMOVE( fxFly ); + } + + return false; +} + +/* +================ +hhMoveable::ValidateEntity +================ +*/ +idEntity* hhMoveable::ValidateEntity( const int collisionEntityNum ) { + if( collisionEntityNum >= ENTITYNUM_WORLD ) { + return NULL; + } + + return gameLocal.entities[collisionEntityNum]; +} + +/* +================ +hhMoveable::DetermineCollisionSpeed + +Used so we only use linear velocity in our equations +================ +*/ +float hhMoveable::DetermineCollisionSpeed( const idEntity* entity, const idVec3& point1, const idVec3& velocity1, const idVec3& point2, const idVec3& velocity2 ) { + idVec3 originVector = point1 - point2; + originVector.Normalize(); + idVec3 reversedOriginVector = -originVector; + float vel1Dot = (velocity1 * reversedOriginVector); + float vel2Dot = (velocity2 * originVector); + + if( vel2Dot < 0.0f ) { + return 0.0f; + } + + idVec3 adjustedVel1 = vel1Dot * reversedOriginVector; + idVec3 adjustedVel2 = vel2Dot * originVector; + + //PLAYER_COLLISION_PRINTF(entity)( "Vel1: %s, Vel2: %s\n", adjustedVel1.ToString(), adjustedVel2.ToString() ); + + return (adjustedVel2 - adjustedVel1).Length(); +} + +/* +================ +hhMoveable::ApplyImpulse +================ +*/ +void hhMoveable::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + if ( notPushableAI && ent && ent->IsType( idAI::Type ) ) { + return; + } + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +void hhMoveable::AllowImpact( bool allow ) { + if ( allow ) { + physicsObj.EnableImpact(); + } else { + physicsObj.DisableImpact(); + } +} + +/* +======================== +hhMoveable::HoverTo +======================== +*/ +void hhMoveable::Event_HoverTo( const idVec3 &position ) { + + if ( !hoverController ) { + const char *controllerDef = spawnArgs.GetString( "def_hoverController", NULL ); + + if ( controllerDef ) { + hoverController = static_cast( gameLocal.SpawnObject( controllerDef ) ); + } + else { + hoverController = NULL; + } + } + + //gameLocal.Printf( "Hover to %s %.2f\n", position.ToString(), (float) GetPhysics()->GetMass() ); + if ( !hoverController ) { + gameLocal.Warning( "Event_HoverTo: Tried to hover with an invalid def_hoverController" ); + return; + } + + float tension; + tension = GetPhysics()->GetMass() / 64.0 * spawnArgs.GetFloat( "hover_tension", ".01" ); + + hoverPosition = position; + if ( ! spawnArgs.GetBool( "hover_gravity", "0" ) || gameLocal.GetGravityNormal().Compare(vec3_origin, VECTOR_EPSILON ) ) { + hoverAngle = GetPhysics()->GetOrigin() - position; + } + else { + hoverAngle = gameLocal.GetGravityNormal(); + } + hoverAngle.Normalize(); + + hoverController->SetTension( tension ); + hoverController->SetOrigin( hoverPosition ); + hoverController->Attach( this, true ); + + float rotation = spawnArgs.GetFloat( "hover_rotation", "0" ); + if ( rotation ) { + GetPhysics()->SetAngularVelocity( idVec3( rotation, rotation, rotation )); + } + + PostEventMS( &EV_HoverMove, 100 ); +} + + +/* +===================== +hhMoveable::HoverMove +===================== +*/ +void hhMoveable::Event_HoverMove( ) { + float dist; + float freq; // (in Hz) + float period; + float offset; + float normalizedT; + + + if ( !hoverController ) { + gameLocal.Warning( "Event_HoverMove: Tried to hover move with an invalid def_hoverController" ); + return; + } + + // Get the total distance to travel + //! Cache these + float height = GetPhysics()->GetBounds()[ 1 ][ 2 ] - GetPhysics()->GetBounds()[ 0 ][ 2 ]; + dist = spawnArgs.GetFloat( "hover_height_frac", ".1" ) * height; + freq = spawnArgs.GetFloat( "hover_freq", ".5" ); + + if ( !freq || !dist ) { + return; + } + + period = 1 / freq; + + // Find out what offset we should be at this point in time. + normalizedT = ( gameLocal.time % (int) ( period * 1000 ) ) / (float) ( period * 1000 ); + offset = idMath::Sin( normalizedT * idMath::TWO_PI ) * dist; + + // Set our new position + hoverController->SetOrigin( hoverPosition + hoverAngle * offset ); + + // Lets do it again in 1/10th of a second + PostEventMS( &EV_HoverMove, 100 ); +} + + +/* +===================== +hhMoveable::Unhover +===================== +*/ +void hhMoveable::Event_Unhover( ) { + + if ( !hoverController ) { + gameLocal.Warning( "Event_Unhover: Tried to unhover with an invalid def_hoverController" ); + return; + } + //gameLocal.Printf( "Unhover man" ); + + hoverController->Detach( ); + + SAFE_REMOVE( hoverController ); + CancelEvents( &EV_HoverMove ); +} + +/* +================ +hhMoveable::Event_Touch +================ +*/ +void hhMoveable::Event_Touch( idEntity *other, trace_t *trace ) { + if (spawnArgs.GetBool("walkthrough")) { + idVec3 otherVel = other->GetPhysics()->GetLinearVelocity(); + float otherSpeed = otherVel.NormalizeFast(); + if (otherSpeed > 50.0f && GetPhysics()->IsAtRest()) { + idVec3 toSide = hhUtils::RandomSign() * other->GetAxis()[1]; + idVec3 toMoveable = GetOrigin() - other->GetOrigin(); + toMoveable.NormalizeFast(); + idVec3 newVel = ( 3*otherVel + 6*toSide + hhUtils::RandomVector() ) * (1.0f/10.0f); + newVel.z = 0.1f; + newVel.NormalizeFast(); + newVel *= otherSpeed*1.5f; + GetPhysics()->SetLinearVelocity(newVel); + } + } +} + +/* +================ +hhMoveable::Event_SpawnFxFlyLocal + +CJR: Based upon the code in projectiles +================ +*/ +void hhMoveable::Event_SpawnFxFlyLocal( const char* defName ) { + if( !defName || !defName[0] ) { + return; + } + + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo ); +} + +/* +============ +hhMoveable::Event_StartFadingOut() +============ +*/ +void hhMoveable::Event_StartFadingOut(float fadetime) { + float scale = renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM1]; + fadeAlpha.Init( gameLocal.time, SEC2MS(fadetime), scale > 0.0f ? scale : 1.0f, 0.01f ); + BecomeActive( TH_TICKER ); +} + +/* +============ +hhMoveable::Ticker() +============ +*/ +void hhMoveable::Ticker( void ) { + SetDeformation( DEFORMTYPE_SCALE, fadeAlpha.GetCurrentValue(gameLocal.time) ); +} + diff --git a/neo/prey/Prey/game_moveable.h b/neo/prey/Prey/game_moveable.h new file mode 100644 index 00000000..60455344 --- /dev/null +++ b/neo/prey/Prey/game_moveable.h @@ -0,0 +1,56 @@ +#ifndef __HH_MOVEABLE_H +#define __HH_MOVEABLE_H + + +class hhMoveable: public idMoveable { + CLASS_PROTOTYPE( hhMoveable ); + +public: + hhMoveable(); + ~hhMoveable(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void SquishedByDoor(idEntity *door); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void AllowImpact( bool allow ); + + virtual void Event_HoverTo( const idVec3& position ); + virtual void Event_HoverMove( ); + virtual void Event_Unhover( ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_SpawnFxFlyLocal( const char* defName ); + + // HUMANHEAD mdl: For the keeper + ID_INLINE void SetDamageDef( const char *damageDef ) { damage = damageDef; } + +protected: + virtual idEntity* ValidateEntity( const int collisionEntityNum ); + virtual float DetermineCollisionSpeed( const idEntity* entity, const idVec3& point1, const idVec3& velocity1, const idVec3& point2, const idVec3& velocity2 ); + virtual s_channelType DetermineNextChannel(); + + virtual void AttemptToPlayBounceSound( const trace_t &collision, const idVec3 &velocity ); + virtual void Ticker( void ); + void Event_StartFadingOut(float fadetime); + +protected: + bool removeOnCollision; + bool notPushableAI; + + float collisionSpeed_min; + int currentChannel; + int nextDamageTime; // next time movable is allowed to cause collision damage + + hhBindController * hoverController; + idVec3 hoverPosition; + idVec3 hoverAngle; + + idEntityPtr fxFly; + idInterpolate fadeAlpha; +}; + + +#endif diff --git a/neo/prey/Prey/game_mover.cpp b/neo/prey/Prey/game_mover.cpp new file mode 100644 index 00000000..7fcecc19 --- /dev/null +++ b/neo/prey/Prey/game_mover.cpp @@ -0,0 +1,241 @@ +// game_mover.cpp +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//============================================================================= +// hhMover +//============================================================================= + +CLASS_DECLARATION( idMover, hhMover ) +END_CLASS + + +void hhMover::Spawn(void) { + + if ( GetPhysics()->GetClipModel() && spawnArgs.GetBool( "unblockable", "0" ) ) { + // HUMANHEAD pdm: forcing unblockable off when noclipmodel is set, not valid to have push without clipmodel + //gameLocal.Printf( "Setting to nopushsupported" ); + physicsObj.SetPusher( 0 | PUSHFL_UNBLOCKABLE ); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + BecomeNonSolid(); + } + +} + +void hhMover::BecomeNonSolid() { + // Somewhat copied from idMoveable + physicsObj.SetContents( CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( 0 ); +} + + +//============================================================================= +// hhMoverWallwalk +// Mover capable of doing wallwalk fading +//============================================================================= + +CLASS_DECLARATION(hhMover, hhMoverWallwalk) + EVENT(EV_Activate, hhMoverWallwalk::Event_Activate) +END_CLASS + +void hhMoverWallwalk::Spawn(void) { + wallwalkOn = spawnArgs.GetBool("active"); + flicker = spawnArgs.GetBool("flicker"); + + // Get skin references (already precached) + onSkin = declManager->FindSkin( spawnArgs.GetString("skinOn") ); + offSkin = declManager->FindSkin( spawnArgs.GetString("skinOff") ); + + if (wallwalkOn) { + SetSkin( onSkin ); + alphaOn.Init(gameLocal.time, 0, 1.0f, 1.0f); + SetShaderParm(5, 1); + } + else { + SetSkin( offSkin ); + alphaOn.Init(gameLocal.time, 0, 0.0f, 0.0f); + SetShaderParm(5, 0); + } + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + UpdateVisuals(); +} + +void hhMoverWallwalk::Save(idSaveGame *savefile) const { + savefile->WriteFloat( alphaOn.GetStartTime() ); // hhHermiteInterpolate + savefile->WriteFloat( alphaOn.GetDuration() ); + savefile->WriteFloat( alphaOn.GetStartValue() ); + savefile->WriteFloat( alphaOn.GetEndValue() ); + savefile->WriteFloat( alphaOn.GetS1() ); + savefile->WriteFloat( alphaOn.GetS2() ); + + savefile->WriteBool(wallwalkOn); + savefile->WriteBool(flicker); + savefile->WriteSkin(onSkin); + savefile->WriteSkin(offSkin); +} + +void hhMoverWallwalk::Restore( idRestoreGame *savefile ) { + float set, set2; + + savefile->ReadFloat( set ); // hhHermiteInterpolate + alphaOn.SetStartTime( set ); + savefile->ReadFloat( set ); + alphaOn.SetDuration( set ); + savefile->ReadFloat( set ); + alphaOn.SetStartValue(set); + savefile->ReadFloat( set ); + alphaOn.SetEndValue( set ); + savefile->ReadFloat( set ); + savefile->ReadFloat( set2 ); + alphaOn.SetHermiteParms(set, set2); + + savefile->ReadBool(wallwalkOn); + savefile->ReadBool(flicker); + savefile->ReadSkin(onSkin); + savefile->ReadSkin(offSkin); +} + +void hhMoverWallwalk::Think() { + hhMover::Think(); + if (thinkFlags & TH_THINK) { + SetShaderParm(4, alphaOn.GetCurrentValue(gameLocal.time)); + if (alphaOn.IsDone(gameLocal.time)) { + BecomeInactive(TH_THINK); + if (!wallwalkOn) { + SetSkin( offSkin ); + SetShaderParm(5, 0); // Not completely on + } + else { + SetShaderParm(5, 1); // Tell material to stay on + } + } + } +} + +void hhMoverWallwalk::SetWallWalkable(bool on) { + wallwalkOn = on; + + float curAlpha = alphaOn.GetCurrentValue(gameLocal.time); + + if (wallwalkOn) { // Turning on + BecomeActive(TH_THINK); + SetSkin( onSkin ); + StartSound( "snd_powerup", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 1.0f ); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, WALLWALK_HERM_S2); + SetShaderParm(5, 0); // Not completely on + } + else { // Turning off + BecomeActive(TH_THINK); + StartSound( "snd_powerdown", SND_CHANNEL_ANY ); + alphaOn.Init(gameLocal.time, WALLWALK_TRANSITION_TIME, curAlpha, 0.0f); + alphaOn.SetHermiteParms(WALLWALK_HERM_S1, 1.0f); // no overshoot + SetShaderParm(5, 0); // Not completely on + } +} + +void hhMoverWallwalk::Event_Activate(idEntity *activator) { + SetWallWalkable(!wallwalkOn); +} + + +//============================================================================= +//============================================================================= +// +// hhExplodeMover +// +// Mover explodes from a specific point to the destination when triggered +//============================================================================= +//============================================================================= + +CLASS_DECLARATION( hhMover, hhExplodeMover ) + EVENT( EV_PostSpawn, hhExplodeMover::Event_PostSpawn ) + EVENT( EV_Activate, hhExplodeMover::Event_Trigger ) +END_CLASS + +void hhExplodeMover::Spawn(void) { + moveDelay = spawnArgs.GetFloat( "moveDelay", "0" ); + PostEventMS( &EV_PostSpawn, 0 ); +} + +void hhExplodeMover::Event_PostSpawn( void ) { + // Set up the destination location after this object is triggered + dest_position = spawnArgs.GetVector( "destOrigin", "0 0 0" ); +} + +void hhExplodeMover::Save(idSaveGame *savefile) const { + savefile->WriteInt(oldContents); + savefile->WriteInt(oldClipMask); + savefile->WriteFloat(moveDelay); +} + +void hhExplodeMover::Restore( idRestoreGame *savefile ) { + savefile->ReadInt(oldContents); + savefile->ReadInt(oldClipMask); + savefile->ReadFloat(moveDelay); +} + +void hhExplodeMover::Event_Trigger( idEntity *activator ) { + // When triggered, blast self to the original position + oldContents = GetPhysics()->GetContents(); + GetPhysics()->SetContents( 0 ); + + oldClipMask = GetPhysics()->GetClipMask(); + GetPhysics()->SetClipMask( 0 ); + + BeginMove( NULL ); +} + +void hhExplodeMover::DoneMoving( void ) { + idMover::DoneMoving(); + GetPhysics()->SetContents( oldContents ); + GetPhysics()->SetClipMask( oldClipMask ); +} + +//============================================================================= +//============================================================================= +// +// hhExplodeMoverOrigin +// +// Origin object for exploding movers. +//============================================================================= +//============================================================================= + +CLASS_DECLARATION( idEntity, hhExplodeMoverOrigin ) + EVENT( EV_Activate, hhExplodeMoverOrigin::Event_Trigger ) +END_CLASS + +void hhExplodeMoverOrigin::Spawn(void) { +} + +void hhExplodeMoverOrigin::Event_Trigger( idEntity *activator ) { + int count = 0; + +/*FIXME: Should use this format instead for speed +for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idLight::Type ) ) { + idLight *light = static_cast(ent); + } +}*/ + // When triggered, locate explode movers than have this as their origin entity, and then trigger them + for (int i = 0; i < gameLocal.num_entities; i++ ) { + idEntity *ent = gameLocal.entities[i]; + if ( !ent || !ent->IsType( hhExplodeMover::Type ) ) { + continue; + } + + hhExplodeMover *mover = static_cast(ent); + if( mover->targets.Num() > 0 && mover->targets[0].GetEntity() == this ) { // Trigger all explode movers that have this origin as a target + mover->PostEventSec( &EV_Activate, mover->GetMoveDelay(), this ); + count++; + } + } +} diff --git a/neo/prey/Prey/game_mover.h b/neo/prey/Prey/game_mover.h new file mode 100644 index 00000000..eff2fd2b --- /dev/null +++ b/neo/prey/Prey/game_mover.h @@ -0,0 +1,73 @@ +#ifndef __PREY_MOVER_H__ +#define __PREY_MOVER_H__ + + +class hhMover : public idMover { + CLASS_PROTOTYPE( hhMover ); + +public: + void Spawn(); + void BecomeNonSolid(); + void Save( idSaveGame *savefile ) const { } + void Restore( idRestoreGame *savefile ) { Spawn(); } +}; + + +class hhMoverWallwalk : public hhMover { + CLASS_PROTOTYPE(hhMoverWallwalk); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SetWallWalkable(bool on); + void Think(); + +protected: + void Event_Activate(idEntity *activator); + +protected: + hhHermiteInterpolate alphaOn; // Degree to which wallwalk is on + bool wallwalkOn; + bool flicker; + const idDeclSkin *onSkin; + const idDeclSkin *offSkin; +}; + + +//============================================================================= + +class hhExplodeMover : public hhMover { + CLASS_PROTOTYPE( hhExplodeMover ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void DoneMoving( void ); + + float GetMoveDelay( void ) { return moveDelay; } + +protected: + void Event_PostSpawn( void ); + void Event_Trigger( idEntity *activator ); + +protected: + int oldContents; // contents before moving (contents are none while moving) + int oldClipMask; + float moveDelay; +}; + +class hhExplodeMoverOrigin : public idEntity { + CLASS_PROTOTYPE( hhExplodeMoverOrigin ); + +public: + void Spawn(); +protected: + void Event_PostSpawn( void ); + void Event_Trigger( idEntity *activator ); +}; + +//============================================================================= + +#endif // __PREY_MOVER_H__ diff --git a/neo/prey/Prey/game_note.cpp b/neo/prey/Prey/game_note.cpp new file mode 100644 index 00000000..9311a9b6 --- /dev/null +++ b/neo/prey/Prey/game_note.cpp @@ -0,0 +1,29 @@ +// Game_note.cpp +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +CLASS_DECLARATION(hhConsole, hhNote) +END_CLASS + + +void hhNote::Spawn(void) { + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->SetStateString("guitext", spawnArgs.GetString("text", "no text")); + + // For overriding the text that is defined in the Map file. Used when we put a note + // in as a placeholder for obsolete items. The text key may already be used (consoles), + // so use this to override the text. + idStr override; + if (spawnArgs.GetString("textoverride", "", override)) { + renderEntity.gui[0]->SetStateString("guitext", override.c_str()); + } + } +} + + diff --git a/neo/prey/Prey/game_note.h b/neo/prey/Prey/game_note.h new file mode 100644 index 00000000..7760f899 --- /dev/null +++ b/neo/prey/Prey/game_note.h @@ -0,0 +1,13 @@ + +#ifndef __GAME_NOTE_H__ +#define __GAME_NOTE_H__ + +class hhNote : public hhConsole { +public: + CLASS_PROTOTYPE( hhNote ); + + void Spawn( void ); +}; + + +#endif /* __GAME_NOTE_H__ */ diff --git a/neo/prey/Prey/game_organtrigger.cpp b/neo/prey/Prey/game_organtrigger.cpp new file mode 100644 index 00000000..4165d7c6 --- /dev/null +++ b/neo/prey/Prey/game_organtrigger.cpp @@ -0,0 +1,190 @@ +#include "precompiled.h" +#pragma hdrstop + +/* +add particles to bone +make wait: -1 work (removes self now) +*/ + +#include "prey_local.h" + +const idEventDef EV_PlayPainIdle("", NULL); +const idEventDef EV_ResetOrgan("", NULL); + +CLASS_DECLARATION( hhAnimatedEntity, hhOrganTrigger ) + EVENT( EV_Enable, hhOrganTrigger::Event_Enable ) + EVENT( EV_Disable, hhOrganTrigger::Event_Disable ) + EVENT( EV_PlayIdle, hhOrganTrigger::Event_PlayIdle) + EVENT( EV_PlayPainIdle, hhOrganTrigger::Event_PlayPainIdle) + EVENT( EV_ResetOrgan, hhOrganTrigger::Event_ResetOrgan) + EVENT( EV_PostSpawn, hhOrganTrigger::Event_PostSpawn ) +END_CLASS + + +//-------------------------------- +// hhOrganTrigger::~hhOrganTrigger +//-------------------------------- +hhOrganTrigger::~hhOrganTrigger() { + SAFE_REMOVE( trigger ); +} + +//-------------------------------- +// hhOrganTrigger::Event_PostSpawn +//-------------------------------- +void hhOrganTrigger::Event_PostSpawn() { + SpawnTrigger(); +} + +//-------------------------------- +// hhOrganTrigger::Spawn +//-------------------------------- +void hhOrganTrigger::Spawn( void ) { + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SOLID ); + + idleAnim = GetAnimator()->GetAnim( "idle" ); + painAnim = GetAnimator()->GetAnim( "pain" ); + resetAnim = GetAnimator()->GetAnim( "reset" ); + painIdleAnim = GetAnimator()->GetAnim( "painidle" ); + + trigger = NULL; + if (!gameLocal.isClient) { + if (gameLocal.isMultiplayer) { + PostEventMS(&EV_PostSpawn, 0); + } else { //spawn right now. i don't want to break save/load stuff... + SpawnTrigger(); + } + } + + ProcessEvent( &EV_PlayIdle ); + + ProcessEvent( (spawnArgs.GetBool("enabled", "1")) ? &EV_Enable : &EV_Disable ); +} + +void hhOrganTrigger::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( resetAnim ); + savefile->WriteInt( painIdleAnim ); + trigger.Save(savefile); +} + +void hhOrganTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( resetAnim ); + savefile->ReadInt( painIdleAnim ); + trigger.Restore(savefile); +} + +//-------------------------------- +// hhOrganTrigger::SpawnTrigger +//-------------------------------- +void hhOrganTrigger::SpawnTrigger() { + idDict args = spawnArgs; + + args.SetVector( "mins", idVec3(-1.0f, -1.0f, -1.0f) ); + args.SetVector( "maxs", idVec3(1.0f, 1.0f, 1.0f) ); + args.SetBool( "noTouch", true ); + args.SetBool( "enabled", false ); + args.Delete( "spawnclass" ); + args.Delete( "name" ); + args.Delete( "model" ); + trigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &args ); + if( trigger.IsValid() ) { + //Bound entities are removed when their master is removed + trigger->Bind( this, true ); + } +} + +//-------------------------------- +// hhOrganTrigger::Killed +//-------------------------------- +void hhOrganTrigger::Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, painAnim, gameLocal.GetTime(), 100 ); + int opentime = GetAnimator()->GetAnim( painAnim )->Length(); + PostEventMS( &EV_PlayPainIdle, opentime ); + + if( !trigger.IsValid() ) { + return; + } + + trigger->TriggerAction( attacker ); + + Disable(); + + if( trigger->wait >= 0 ) { + PostEventMS( &EV_ResetOrgan, trigger->nextTriggerTime - gameLocal.GetTime() ); + } +} + +//-------------------------------- +// hhOrganTrigger::Enable +//-------------------------------- +void hhOrganTrigger::Enable() { + fl.takedamage = true; + + if( trigger.IsValid() ) { + trigger->Enable(); + } +} + +//-------------------------------- +// hhOrganTrigger::Disable +//-------------------------------- +void hhOrganTrigger::Disable() { + fl.takedamage = false; + + if( trigger.IsValid() ) { + trigger->Disable(); + } +} + +//-------------------------------- +// hhOrganTrigger::Event_Enable +//-------------------------------- +void hhOrganTrigger::Event_Enable() { + Enable(); +} + +//-------------------------------- +// hhOrganTrigger::Event_Disable +//-------------------------------- +void hhOrganTrigger::Event_Disable() { + Disable(); +} + +//-------------------------------- +// hhOrganTrigger::Event_PlayIdle +//-------------------------------- +void hhOrganTrigger::Event_PlayIdle( void ) { + Enable(); + + if( idleAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.GetTime(), 0 ); + } +} + +//-------------------------------- +// hhOrganTrigger::Event_PlayPainIdle +//-------------------------------- +void hhOrganTrigger::Event_PlayPainIdle( void ) { + if( painIdleAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, painIdleAnim, gameLocal.GetTime(), 100 ); + } +} + +//-------------------------------- +// hhOrganTrigger::Event_ResetOrgan +//-------------------------------- +void hhOrganTrigger::Event_ResetOrgan( void ) { + if( resetAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, resetAnim, gameLocal.GetTime(), 100 ); + int opentime = GetAnimator()->GetAnim( resetAnim )->Length(); + PostEventMS( &EV_PlayIdle, opentime ); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/game_organtrigger.h b/neo/prey/Prey/game_organtrigger.h new file mode 100644 index 00000000..38874299 --- /dev/null +++ b/neo/prey/Prey/game_organtrigger.h @@ -0,0 +1,41 @@ +#ifndef __GAME_ORGANTRIGGER_H__ +#define __GAME_ORGANTRIGGER_H__ + +extern const idEventDef EV_ModelDoorOpen; +extern const idEventDef EV_ModelDoorClose; + +class hhOrganTrigger : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhOrganTrigger ); + + virtual ~hhOrganTrigger(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location ); + +protected: + void SpawnTrigger(); + + virtual void Enable(); + virtual void Disable(); + + void Event_Enable(); + void Event_Disable(); + void Event_PlayIdle( void ); + void Event_PlayPainIdle( void ); + void Event_ResetOrgan( void ); + virtual void Event_PostSpawn(); + +private: + int idleAnim; + int painAnim; + int resetAnim; + int painIdleAnim; + + idEntityPtr trigger; +}; + +#endif /* __GAME_ORGANTRIGGER_H__ */ diff --git a/neo/prey/Prey/game_player.cpp b/neo/prey/Prey/game_player.cpp new file mode 100644 index 00000000..1e80499a --- /dev/null +++ b/neo/prey/Prey/game_player.cpp @@ -0,0 +1,7825 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define DAMAGE_INDICATOR_TIME 1100 // Update this in hud_damageindicator.guifragment too + +const idEventDef EV_PlayWeaponAnim( "playWeaponAnim", "sd" ); +const idEventDef EV_RechargeHealth( "", NULL ); +const idEventDef EV_RechargeRifleAmmo( "", NULL ); +const idEventDef EV_Cinematic( "cinematic", "dd" ); +const idEventDef EV_DialogStart( "dialogStart", "ddd" ); +const idEventDef EV_DialogStop( "dialogStop", NULL ); +const idEventDef EV_LotaTunnelMode( "lotaTunnelMode", "d" ); +const idEventDef EV_DrainSpiritPower( "", NULL ); +const idEventDef EV_SpawnDeathWraith( "", NULL ); +const idEventDef EV_PrepareToResurrect( "prepareToResurrect" ); +const idEventDef EV_ResurrectScreenFade( "" ); +const idEventDef EV_Resurrect( "resurrect" ); +const idEventDef EV_PrepareForDeathWorld( "", NULL ); +const idEventDef EV_EnterDeathWorld( "", NULL ); +const idEventDef EV_AdjustSpiritPowerDeathWalk( "" ); +const idEventDef EV_SetOverlayMaterial( "setOverlayMaterial", "sd" ); +const idEventDef EV_SetOverlayTime( "setOverlayTime", "fd" ); +const idEventDef EV_SetOverlayColor( "setOverlayColor", "ffff" ); +const idEventDef EV_DDAHeartbeat( "", NULL ); +const idEventDef EV_ShouldRemainAlignedToAxial( "shouldRemainAlignedToAxial", "d" ); +const idEventDef EV_StartHudTranslation( "" ); +const idEventDef EV_Unfreeze( "" ); +const idEventDef EV_GetSpiritPower( "getSpiritPower", "", 'f' ); //rww +const idEventDef EV_SetSpiritPower( "setSpiritPower", "f" ); //rww +const idEventDef EV_OnGround( "onGround", NULL, 'f' ); // bg +const idEventDef EV_BindUnfroze( "", "e" ); // mdl +const idEventDef EV_LockWeapon( "lockWeapon", "d" ); // mdl +const idEventDef EV_UnlockWeapon( "unlockWeapon", "d" ); // mdl +const idEventDef EV_SetPrivateCameraView( "setPrivateCameraView", "Ed" ); //rdr +const idEventDef EV_SetCinematicFOV( "setCinematicFOV", "ffff" ); //rdr +const idEventDef EV_StopSpiritWalk( "stopSpiritWalk" ); //rww +const idEventDef EV_DamagePlayer( "damagePlayer", "eevsfd" ); //rww +const idEventDef EV_GetSpiritProxy( "getSpiritProxy", "", 'e' ); //jsh +const idEventDef EV_IsSpiritWalking( "isSpiritWalking", "", 'd' ); // pdm +const idEventDef EV_IsDeathWalking( "isDeathWalking", "", 'd' ); // bjk +const idEventDef EV_GetDDAValue( "getDDAValue", "", 'f' ); // cjr +const idEventDef EV_AllowLighter( "allowLighter", "d" ); // jsh +const idEventDef EV_DisableSpirit( "disableSpirit" ); // mdl +const idEventDef EV_EnableSpirit( "enableSpirit" ); // mdl +const idEventDef EV_UpdateDDA( "" ); // cjr +const idEventDef EV_AllowDamage( "" ); // mdl +const idEventDef EV_IgnoreDamage( "" ); // mdl +const idEventDef EV_RespawnCleanup( "" ); //rww +const idEventDef EV_ReturnToWeapon( "returnToWeapon", "", 'd' ); //bjk +const idEventDef EV_CanAnimateTorso( "canAnimateTorso", "", 'd' ); //rww + +CLASS_DECLARATION( idPlayer, hhPlayer ) + EVENT( EV_PlayWeaponAnim, hhPlayer::Event_PlayWeaponAnim ) + EVENT( EV_RechargeHealth, hhPlayer::Event_RechargeHealth ) + EVENT( EV_RechargeRifleAmmo, hhPlayer::Event_RechargeRifleAmmo ) + EVENT( EV_Cinematic, hhPlayer::Event_Cinematic ) + EVENT( EV_DialogStart, hhPlayer::Event_DialogStart ) + EVENT( EV_DialogStop, hhPlayer::Event_DialogStop ) + EVENT( EV_LotaTunnelMode, hhPlayer::Event_LotaTunnelMode ) + EVENT( EV_DrainSpiritPower, hhPlayer::Event_DrainSpiritPower ) + EVENT( EV_SpawnDeathWraith, hhPlayer::Event_SpawnDeathWraith ) + EVENT( EV_PrepareToResurrect, hhPlayer::Event_PrepareToResurrect ) + EVENT( EV_ResurrectScreenFade, hhPlayer::Event_ResurrectScreenFade ) + EVENT( EV_Resurrect, hhPlayer::Event_Resurrect ) + EVENT( EV_EnterDeathWorld, hhPlayer::Event_EnterDeathWorld ) + EVENT( EV_PrepareForDeathWorld, hhPlayer::Event_PrepareForDeathWorld ) + EVENT( EV_AdjustSpiritPowerDeathWalk, hhPlayer::Event_AdjustSpiritPowerDeathWalk ) + EVENT( EV_ShouldRemainAlignedToAxial, hhPlayer::Event_ShouldRemainAlignedToAxial ) + EVENT( EV_OrientToGravity, hhPlayer::Event_OrientToGravity ) + EVENT( EV_ResetGravity, hhPlayer::Event_ResetGravity ) + EVENT( EV_SetOverlayMaterial, hhPlayer::Event_SetOverlayMaterial ) + EVENT( EV_SetOverlayTime, hhPlayer::Event_SetOverlayTime ) + EVENT( EV_SetOverlayColor, hhPlayer::Event_SetOverlayColor ) + EVENT( EV_DDAHeartbeat, hhPlayer::Event_DDAHeartBeat ) + EVENT( EV_StartHudTranslation, hhPlayer::Event_StartHUDTranslation) + EVENT( EV_Unfreeze, hhPlayer::Event_Unfreeze) + EVENT( EV_GetSpiritPower, hhPlayer::Event_GetSpiritPower) //rww + EVENT( EV_SetSpiritPower, hhPlayer::Event_SetSpiritPower) //rww + EVENT( EV_OnGround, hhPlayer::Event_OnGround ) // bg + EVENT( EV_LockWeapon, hhPlayer::Event_LockWeapon ) // mdl + EVENT( EV_UnlockWeapon, hhPlayer::Event_UnlockWeapon ) // mdl + EVENT( EV_SetPrivateCameraView, hhPlayer::Event_SetPrivateCameraView ) //rdr + EVENT( EV_SetCinematicFOV, hhPlayer::Event_SetCinematicFOV ) //rdr + EVENT( EV_StopSpiritWalk, hhPlayer::Event_StopSpiritWalk ) //rww + EVENT( EV_DamagePlayer, hhPlayer::Event_DamagePlayer ) //rww + EVENT( EV_GetSpiritProxy, hhPlayer::Event_GetSpiritProxy ) //rww + EVENT( EV_IsSpiritWalking, hhPlayer::Event_IsSpiritWalking ) //pdm + EVENT( EV_IsDeathWalking, hhPlayer::Event_IsDeathWalking ) //bjk + EVENT( EV_GetDDAValue, hhPlayer::Event_GetDDAValue ) // cjr + EVENT( EV_AllowLighter, hhPlayer::Event_AllowLighter ) // jsh + EVENT( EV_DisableSpirit, hhPlayer::Event_DisableSpirit ) // mdl + EVENT( EV_EnableSpirit, hhPlayer::Event_EnableSpirit ) // mdl + EVENT( EV_UpdateDDA, hhPlayer::Event_UpdateDDA ) // cjr + EVENT( EV_AllowDamage, hhPlayer::Event_AllowDamage ) // mdl + EVENT( EV_IgnoreDamage, hhPlayer::Event_IgnoreDamage ) // mdl + EVENT( EV_RespawnCleanup, hhPlayer::Event_RespawnCleanup ) //rww + EVENT( EV_ReturnToWeapon, hhPlayer::Event_ReturnToWeapon ) //bjk + EVENT( EV_CanAnimateTorso, hhPlayer::Event_CanAnimateTorso ) //rww +END_CLASS + +//rww +CLASS_DECLARATION( hhPlayer, hhArtificialPlayer ) +END_CLASS + +/* +================= +hhPlayer::hhPlayer +================= +*/ +hhPlayer::hhPlayer( void ) : + thirdPersonCameraClipBounds( idTraceModel(idBounds(idVec3(-4, -8, -8), idVec3(8, 8, 8))) ) { + + hand = NULL; // nla + spiritProxy = NULL; + possessedTommy = NULL; + talon = NULL; + handNext = NULL; + deathLookAtEntity = NULL; + guiWantsControls = NULL; + lighterHandle = -1; + nextTalonAttackCommentTime = 0; + bTalonAttackComment = false; + bSpiritWalk = false; + bDeathWalk = false; + bReallyDead = false; + bPossessed = false; + bInCinematic = false; + bFrozen = false; + bScopeView = false; //rww + bInDeathwalkTransition = false; + lastAppliedBobCycle = 0; +#if GAMEPAD_SUPPORT // VENOM BEGIN + lastAutoLevelTime = 0; + lastAccelTime = 0; + lastAccelFactor = 1.0f; +#endif // VENOM END + cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, 90.f, 90.f ); //rdr + mpHitFeedbackTime = 0; //rww + bAllowSpirit = true; //mdl + bCollidingWithPortal = false; + bPlayingLowHealthSound = false; + bLotaTunnelMode = false; + + for (int ix=0; ix 8 ) { + // Go to the highest weapon available if no weapon is current selected (NOTE: This will be ignored if all weapons are locked) + NextBestWeapon(); //HUMANHEAD bjk + } + + //HUMANHEAD PCF mdl 04/26/06 - Made this a separate if block from above so we can disallow locked weapons + if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) { + // If the current weapon is invalid now, try the next weapon + NextWeapon(); + if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) { + // No weapons available + if ( weapon.GetEntity() ) { + weapon.GetEntity()->PutAway(); + weapon.GetEntity()->HideWeapon(); + } + idealWeapon = 0; + currentWeapon = -1; + } + } + + nextSpiritTime = 0; + + mpHitFeedbackTime = 0; //rww + + airAttackerTime = 0; + bDeathWalkStage2 = false; + + physicsObj.SetInwardGravity(-1); //rww +} + +void hhPlayer::RestorePersistantInfo( void ) { + int num; + + idPlayer::RestorePersistantInfo(); + + // Update persistent amoo maximums + num = GetWeaponNum("weaponobj_soulstripper"); + assert(num); + weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_energy" ); + + num = GetWeaponNum("weaponobj_bow"); + assert(num); + weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_spiritpower" ); +} + +/* +============== +hhPlayer::SpawnTalon() +============== +*/ +void hhPlayer::TrySpawnTalon() { + // TODO: Deal with co-op issues. Have multiple Talons in co-op, or just one? + if (inventory.requirements.bCanSummonTalon && !talon.IsValid()) { + talon = (hhTalon *)gameLocal.SpawnObject( spawnArgs.GetString( "def_hawkpower" ), NULL ); + if( talon.IsValid() ) { + talon->SetOwner( this ); + talon->SummonTalon(); + bTalonAttackComment = true; + nextTalonAttackCommentTime = 0; + } + } +} + +/* +============== +hhPlayer::TalonAttackComment + +Talon is attacking an enemy, so have Tommy make a comment +============== +*/ +void hhPlayer::TalonAttackComment() { + if ( IsDeathWalking() ) { + return; + } + + // Play a sound on the player to encourage Talon to attack + if ( bTalonAttackComment && gameLocal.GetTime() > nextTalonAttackCommentTime ) { // Player can make an attack comment + int num = spawnArgs.GetInt( "numAttackComments", "6" ); + + StartSound( va( "snd_talonattack%d", gameLocal.random.RandomInt( num ) ), SND_CHANNEL_VOICE, 0, false, NULL ); + nextTalonAttackCommentTime = gameLocal.GetTime() + spawnArgs.GetInt( "talonAttackCommentTime", "30000" ); + } +} + +/* +============== +hhPlayer::~hhPlayer() + +Release any resources used by the player. +============== +*/ +hhPlayer::~hhPlayer() { + //rww - remove me if i'm a pilot + if (InVehicle() && GetVehicleInterface() && GetVehicleInterface()->GetVehicle()) { + GetVehicleInterface()->GetVehicle()->EjectPilot(); + } + + StopSpiritWalk(); + + RemoveResources(); +} + + +void hhPlayer::RemoveResources() { + LighterOff(); + SAFE_REMOVE( hand ); //FIXME: Should this be something other than a safe remove? (ie, remove hand?) + SAFE_REMOVE( talon ); + SAFE_REMOVE( spiritProxy ); + SAFE_REMOVE( possessedTommy ); +} + + +/* +============== +hhPlayer::Init() + +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 +Called during SpawnToPoint +============== +*/ +void hhPlayer::Init() { + oldCmdAngles.Zero(); + + idPlayer::Init(); + + // initialize the script variables + AI_ASIDE = false; + + bInDeathwalkTransition = false; + bShowProgressBar = false; + progressBarValue = 0.0f; + progressBarState = 0; + progressBarGuiValue.Init(gameLocal.time, 0, 0.0f, 0.0f); + + LighterOff(); //rww - make sure lighter is removed first (primarily for mp) + + bobFrac = 0.0f; + lighterTemperature = 0; + lighterHandle = -1; + bPossessed = false; // cjr - player is unpossessed at the start + possessionTimer = 0; // cjr - player is unpossessed at the start + possessionFOV = 0; // cjr - no FOV change for possession at start + nextTalonAttackCommentTime = 0; + bTalonAttackComment = false; // cjr - If Tommy can make comments about talon attacking enemies + bSpiritWalk = false; // cjr - initialize spiritwalk variables + spiritProxy = NULL; // cjr - initialize spiritwalk variables + possessedTommy = NULL; // cjr - initialize possessed tommy + lastWeaponSpirit = 0; // cjr - initialize spiritwalk variables + guiWantsControls = NULL; // pdm - gui that controls should be routed to + bClampYaw = false; // pdm - whether do clamp yaw to master's axis (for rail rides) + maxRelativeYaw = 180.0f; // pdm - max deviation from master axis allowed when clamping yaw + maxRelativePitch = 0.0f; // rww - max deviation from master axis allowed when clamping pitch + bInCinematic = false; // pdm - our simple cinematic system (position locking) + lockView = false; + preCinematicWeapon = 0; + preCinematicWeaponFlags = 0; + guiOverlay = NULL; // pdm - current overlay gui + spiritWalkToggleTime = 0; + bLotaTunnelMode = false; + + StopSound(SND_CHANNEL_HEART, false); //rww - make sure this is stopped here, could conceivably still be playing on respawn + bPlayingLowHealthSound = false; + + // Set shuttle view off + if (entityNumber == gameLocal.localClientNum) { //rww + renderSystem->SetShuttleView( false ); + } + + if (gameLocal.isMultiplayer) { //rww - reset my really-deadness when i respawn in MP + bReallyDead = false; + SetSkinByName(NULL); //make sure we are not using spiritwalk skin. + } + + SetViewAnglesSensitivity(1.0f); + cameraInterpolator.SetSelf( this ); + + for (int ix=0; ixIsType(hhForceField::Type) || ent->IsType(hhDeathWraith::Type)) ) { + idPlayer::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +hhPlayer::CL_UpdateProgress + Client function to update progress related variables + called after getting a snapshot, or directly if a local client +================ +*/ +void hhPlayer::CL_UpdateProgress(bool bBar, float value, int state) { + idUserInterface *_hud = NULL; + + if (InVehicle() && GetVehicleInterfaceLocal() && GetVehicleInterfaceLocal()->GetHUD()) { + _hud = GetVehicleInterfaceLocal()->GetHUD(); + } + else { + _hud = hud; + } + + // Update progress bar + if (bBar && !bShowProgressBar) { + _hud->HandleNamedEvent("ShowProgressBar"); + } + else if (!bBar && bShowProgressBar) { + _hud->HandleNamedEvent("HideProgressBar"); + } + + // See if we just entered a victory state + if (state != progressBarState) { + switch (state) { + case 0: + _hud->HandleNamedEvent("ProgressNone"); + break; + case 1: + _hud->HandleNamedEvent("ProgressVictory"); + break; + case 2: + _hud->HandleNamedEvent("ProgressFailure"); + break; + } + } + + // Interpolate gui value to target value + float curValue = progressBarGuiValue.GetCurrentValue(gameLocal.time); + progressBarGuiValue.Init(gameLocal.time, 500, curValue, value); + + // Update variables + bShowProgressBar = bBar; + progressBarValue = value; + progressBarState = state; +} + +// This called once per tick, put things in here that should be updated regardless of frame rate +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhPlayer::UpdateHud( idUserInterface *_hud ) { + idPlayer *aimed; + + if ( !_hud ) { + return; + } + + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + +#define PICKUPTIME_FADEIN_START 0 +#define PICKUPTIME_FADEIN_END 500 +#define PICKUPTIME_FADEOUT_START 1750 +#define PICKUPTIME_FADEOUT_END 2000 +#define MULTIPLEITEM_SPEEDUP 7 + int c = inventory.pickupItemNames.Num(); + int time; + int i; + int pickupTimeFadeInStart, pickupTimeFadeInStop, pickupTimeFadeOutStart, pickupTimeFadeOutStop; + float scale = 1.0f / (((c-1.0f)/9.0f) * (MULTIPLEITEM_SPEEDUP-1.0f) + 1.0f); + pickupTimeFadeInStart = PICKUPTIME_FADEIN_START; + pickupTimeFadeInStop = PICKUPTIME_FADEIN_END; + pickupTimeFadeOutStart = PICKUPTIME_FADEOUT_START * scale; + pickupTimeFadeOutStop = PICKUPTIME_FADEOUT_END * scale; + + // Determine fade in color for icons + if (c > 0) { + for ( i = 0; i < c; i++ ) { + inventory.pickupItemNames[i].time += gameLocal.msec; + time = inventory.pickupItemNames[i].time; + + if (time < pickupTimeFadeInStart) { + inventory.pickupItemNames[i].matcolorAlpha = 0.0f; + } + else if (time < pickupTimeFadeInStop) { + inventory.pickupItemNames[i].matcolorAlpha = ((float)(time-pickupTimeFadeInStart))/(pickupTimeFadeInStop-pickupTimeFadeInStart); + } + else { + inventory.pickupItemNames[i].matcolorAlpha = 1.0f; + } + } + + // Determine fadeout of slot zero icon + inventory.pickupItemNames[0].slotZeroTime += gameLocal.msec; + time = inventory.pickupItemNames[0].slotZeroTime; + if (time < pickupTimeFadeOutStart) { + inventory.pickupItemNames[0].matcolorAlpha = 1.0f; + } + else if (time < pickupTimeFadeOutStop) { + inventory.pickupItemNames[0].matcolorAlpha = 1.0f - ((float)(time-pickupTimeFadeOutStart))/(pickupTimeFadeOutStop-pickupTimeFadeOutStart); + } + else { + inventory.pickupItemNames[0].matcolorAlpha = 0.0f; + } + + // Remove any expired icons + time = inventory.pickupItemNames[0].slotZeroTime; + if (time > pickupTimeFadeOutStop) { + // icon has faded out, remove from list and slide them down + inventory.pickupItemNames.RemoveIndex(0); + } + } + + // HUMANHEAD pdm: Changed aim logic to always show color-coded aimee + if ( MPAim != -1 && gameLocal.entities[ MPAim ] + && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) { + + aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] ); + _hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) ); + idVec4 teamColor = aimed->GetTeamColor(); + _hud->SetStateFloat( "aim_R", teamColor.x ); + _hud->SetStateFloat( "aim_G", teamColor.y ); + _hud->SetStateFloat( "aim_B", teamColor.z ); + + //HUMANHEAD rww - health display on hud for gameplay testing + if (g_showAimHealth.GetBool()) { + _hud->SetStateInt("aim_health", gameLocal.entities[MPAim]->health); + } + //HUMANHEAD END + } + else { + _hud->SetStateString( "aim_text", "" ); + + //HUMANHEAD rww + _hud->SetStateInt("aim_health", 0); + //HUMANHEAD END + } + +#if !GOLD + _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" ); + } +#endif + + if (gameLocal.isNewFrame) { //HUMANHEAD rww - only on newframe + // Update low health sound + if ( health > 0 && health < 25 && !IsSpiritOrDeathwalking() ) { + if (!bPlayingLowHealthSound) { + StartSound("snd_lowhealth", SND_CHANNEL_HEART, 0, false, NULL); + bPlayingLowHealthSound = true; + } + } + else { + if (bPlayingLowHealthSound) { + StopSound(SND_CHANNEL_HEART, false); + bPlayingLowHealthSound = false; + } + } + } + + // Handle damage indictors + idVec3 localDamageVector; + idVec3 toEnemy; + for (int ix=0; ixHandleNamedEvent( va("Attacked%i", ix) );//Notify hud we were attacked + _hud->SetStateBool("displayDamageIndicators", true); + lastAttackers[ix].displayed = true; + } + + toEnemy = lastAttackers[ix].attacker->GetOrigin() - GetOrigin(); + toEnemy.z = 0.0f; + GetAxis().ProjectVector( toEnemy, localDamageVector ); + float yawToEnemy = idMath::AngleNormalize180(localDamageVector.ToYaw()); + _hud->SetStateFloat( va("yawToEnemy%i", ix), yawToEnemy); + if (gameLocal.time > lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) { + lastAttackers[ix].attacker = NULL; + + bool allExpired = true; + for (int jx=0; jxSetStateBool("displayDamageIndicators", false); + } + } + } + } + + if (gameLocal.isMultiplayer) { + bool bLagged = isLagged && gameLocal.isMultiplayer && (gameLocal.localClientNum == entityNumber); + _hud->SetStateBool("hudLag", bLagged); + } + // HUMANHEAD PCF pdm 06-26-06: Fix for multiplayer shuttle hud elements appearing in SP after a MP session + else { + _hud->SetStateBool( "ismultiplayer", false ); + } + // HUMANHEAD END +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhPlayer::UpdateHudWeapon(bool flashWeapon) { + idUserInterface *_hud = hhPlayer::hud; + + // if updating the hud of a followed client + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud ); + _hud = p->hud; + } + } + + if ( _hud ) { + // HUMANHEAD pdm: changed to suit our needs + _hud->SetStateInt( "currentweapon", GetCurrentWeapon() ); + _hud->SetStateInt( "idealweapon", GetIdealWeapon() ); + //HUMANHEAD PCF mdl 05/05/06 - Added !IsLocked( GetIdealWeapon() ) + if ( flashWeapon && !IsLocked( GetIdealWeapon() ) ) { + _hud->HandleNamedEvent( "weaponChange" ); + } + } +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +void hhPlayer::UpdateHudAmmo(idUserInterface *_hud) { + assert( _hud ); + float ammoPct, altPct; + int ammoType, altAmmoType; + float ammo, altAmmo; + bool ammoLow, altAmmoLow; + + // HUMANHEAD pdm: Weapon switch overlay, using rover to spread the expense over frames + static int rover = 0; + if (++rover > 9) { + rover = 0; + } + + if (rover) { + bool bHeld = false; + ammoPct = 0.0f; + altPct = 0.0f; + ammoType = weaponInfo[rover].ammoType; + altAmmoType = altWeaponInfo[rover].ammoType; + ammo = inventory.ammo[ammoType]; + altAmmo = inventory.ammo[altAmmoType]; + ammoLow = false; + altAmmoLow = false; + + if ( inventory.weapons & ( 1 << rover ) ) { + // have this weapon + bHeld = true; + ammoPct = ammo / weaponInfo[rover].ammoMax; + altPct = altAmmo / altWeaponInfo[rover].ammoMax; + ammoLow = ammo > 0 && ammo <= weaponInfo[rover].ammoLow; + altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[rover].ammoLow; + } + + // Spirit ammo doesn't display in a bar + if ( ammoType == 1 ) { + ammoType = 0; + altAmmoType = 0; + } + + // Zero out alt-ammo bar if appropriate + if (altAmmoType == 0 || altAmmoType == ammoType) { + altPct = 0.0f; + } + + _hud->SetStateBool (va("weapon%d_held", rover), bHeld); + _hud->SetStateBool (va("weapon%d_ammolow", rover), ammoLow); + _hud->SetStateBool (va("weapon%d_altammolow", rover), altAmmoLow); + _hud->SetStateFloat(va("weapon%d_ammo", rover), ammoPct); + _hud->SetStateFloat(va("weapon%d_altammo", rover), altPct); + _hud->SetStateBool (va("weapon%d_ammoempty", rover), ammoType != 0 && ammo == 0 && altAmmo == 0); + } + + // Update the current weapon's ammo status + bool bDisallowAmmoBars = false; + ammoPct = 0.0f; + altPct = 0.0f; + ammoLow = false; + altAmmoLow = false; + ammoType = 0; + altAmmoType = 0; + ammo = 0; + altAmmo = 0; + + if( bLotaTunnelMode || privateCameraView || IsLocked(idealWeapon) || !weapon.IsValid() || !weapon->IsLinked() || currentWeapon == -1) { + // Don't display ammo bar for invalid weapons, or when weapons are locked + bDisallowAmmoBars = true; + } + else { + if (currentWeapon >= 0 && currentWeapon < 15) { + ammoType = weaponInfo[currentWeapon].ammoType; + altAmmoType = altWeaponInfo[currentWeapon].ammoType; + ammo = inventory.ammo[ammoType]; + altAmmo = inventory.ammo[altAmmoType]; + ammoPct = ammo / weaponInfo[currentWeapon].ammoMax; + altPct = altAmmo / altWeaponInfo[currentWeapon].ammoMax; + ammoLow = ammo > 0 && ammo <= weaponInfo[currentWeapon].ammoLow; + altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[currentWeapon].ammoLow; + } + + if (ammoType == 1) { + bDisallowAmmoBars = true; + } + } + + if ( bDisallowAmmoBars ) { + _hud->SetStateBool( "player_ammobar", false); + _hud->SetStateBool( "player_altammobar", false ); + } + else { + _hud->SetStateBool( "player_ammobar", ammoType != 0); + _hud->SetStateBool( "player_altammobar", altAmmoType != 0 && altAmmoType != ammoType ); + + _hud->SetStateFloat( "player_ammopercent", ammoPct ); + _hud->SetStateFloat( "player_altammopercent", altPct ); + _hud->SetStateString( "player_ammoamounttext", ammo<0 ? "" : va("%d", ammo) ); + _hud->SetStateString( "player_altammoamounttext", altAmmo<0 ? "" : va("%d", altAmmo) ); + _hud->SetStateBool( "player_ammolow", ammoLow ); + _hud->SetStateBool( "player_altammolow", altAmmoLow ); + } + +} + +/* +================ +hhPlayer::UpdateHudStats + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================ +*/ +void hhPlayer::UpdateHudStats( idUserInterface *_hud ) { + + assert( _hud ); + + int spiritPower = GetSpiritPower(); + + // Disallow health if wrench is locked on a non-lota map --or-- if in a private camera + bool disallowHealth = bLotaTunnelMode || (IsLocked(1) && !gameLocal.IsLOTA()) || privateCameraView != NULL; + bool disallowProgress = bLotaTunnelMode || privateCameraView != NULL || IsDeathWalking(); + + if (disallowHealth) { + int blah = 0; + } + else { + int blah2 = 0; + } + + _hud->SetStateBool("invehicle", InVehicle()); + _hud->SetStateBool("deathwalking", IsDeathWalking()); + _hud->SetStateBool("spiritwalking", IsSpiritWalking()); + _hud->SetStateBool("lighter", IsLighterOn() ); + _hud->SetStateBool("showhealth", !disallowHealth); + _hud->SetStateBool("allowprogress", !disallowProgress); + _hud->SetStateBool("showspiritpower", (!disallowHealth && !InVehicle() && (inventory.requirements.bCanSpiritWalk || IsDeathWalking()))); + _hud->SetStateFloat("lightertemp", lighterTemperature); + _hud->SetStateFloat( "player_spiritpercent", ((float)spiritPower)/inventory.maxSpirit ); + _hud->SetStateFloat( "player_healthpercent", ((float)health)/inventory.maxHealth ); + _hud->SetStateFloat( "player_healthR", inventory.maxHealth > 100 ? 0.75f : 0.6f ); + _hud->SetStateFloat( "player_healthG", inventory.maxHealth > 100 ? 0.85f : 0.0f ); + _hud->SetStateFloat( "player_healthB", inventory.maxHealth > 100 ? 1.0f : 0.0f ); + _hud->SetStateFloat( "player_healthPulseR", inventory.maxHealth > 100 ? 1.0f : 1.0f ); + _hud->SetStateFloat( "player_healthPulseG", inventory.maxHealth > 100 ? 1.0f : 0.0f ); + _hud->SetStateFloat( "player_healthPulseB", inventory.maxHealth > 100 ? 1.0f : 0.0f ); + _hud->SetStateInt( "player_health", health ); + _hud->SetStateInt( "player_maxhealth", inventory.maxHealth ); //rww + _hud->SetStateString( "player_spirit", spiritPower<0 ? "0" : va("%d", spiritPower) ); + _hud->SetStateFloat( "progress", progressBarGuiValue.GetCurrentValue(gameLocal.time) ); + + if ( healthPulse ) { + _hud->HandleNamedEvent( "healthPulse" ); + StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL ); + healthPulse = false; + } + if ( spiritPulse ) { + _hud->HandleNamedEvent( "spiritPulse" ); + StartSound( "snd_spiritpulse", SND_CHANNEL_ITEM, 0, false, NULL ); + spiritPulse = false; + } + + if ( inventory.ammoPulse ) { + _hud->HandleNamedEvent( "ammoPulse" ); + inventory.ammoPulse = false; + } + if ( inventory.weaponPulse ) { + // We need to update the weapon hud manually, but not + // the armor/ammo/health because they are updated every + // frame no matter what + UpdateHudWeapon(); + _hud->HandleNamedEvent( "weaponPulse" ); + inventory.weaponPulse = false; + } + + UpdateHudAmmo( _hud ); + + // Determine slots for our pickup icons + int c = inventory.pickupItemNames.Num(); + for ( int i=0; i<10; i++ ) { + if (iSetStateString( va( "itemicon%i", i ), inventory.pickupItemNames[i].icon ); + _hud->SetStateFloat( va( "itemalpha%i", i ), inventory.pickupItemNames[i].matcolorAlpha ); + _hud->SetStateBool( va( "itemwide%i", i), inventory.pickupItemNames[i].bDoubleWide ); + } + else { + _hud->SetStateString( va( "itemicon%i", i ), "" ); + _hud->SetStateFloat( va( "itemalpha%i", i ), 0.0f ); + _hud->SetStateBool( va( "itemwide%i", i), false ); + } + } +} + +/* +=============== +hhPlayer::DrawHUD + HUMANHEAD: This run only for local players + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::DrawHUD( idUserInterface *_hud ) { + + if (guiOverlay) { + guiOverlay->Redraw(gameLocal.realClientTime); + sysEvent_t ev; + const char *command; + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = guiOverlay->HandleEvent( &ev, gameLocal.time ); +// HandleGuiCommands( this, command ); + return; + } + + // HUMANHEAD pdm: removed weapon ptr check here since ours is invalid when in a vehicle + // Also removed privateCameraView, since we want subtitles when they are on + if ( gameLocal.GetCamera() || !_hud || !g_showHud.GetBool() || pm_thirdPerson.GetBool() ) { + return; + } + + UpdateHudStats( _hud ); + + + if(weapon.IsValid()) { // HUMANHEAD + bool allowGuiUpdate = true; + //rww - update the weapon gui only if the owner is being spectated by this client, or is this client + if ( gameLocal.localClientNum != entityNumber ) { + // if updating the hud for a followed client + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != entityNumber ) { + allowGuiUpdate = false; + } + } else { + allowGuiUpdate = false; + } + } + + if (allowGuiUpdate) { + weapon->UpdateGUI(); + } + } + + _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) ); + + //HUMANHEAD aob: vehicle logic + if ( InVehicle() ) { + DrawHUDVehicle( _hud ); + } + else { + _hud->Redraw( gameLocal.realClientTime ); + } + //HUMANHEAD END + + // weapon targeting crosshair + if ( !GuiActive() ) { + if ( cursor ) { + UpdateCrosshairs(); + cursor->Redraw( gameLocal.realClientTime ); + } + } +} + +/* +=============== +hhPlayer::DrawHUDVehicle + HUMANHEAD: This run only for local players +=============== +*/ +void hhPlayer::DrawHUDVehicle( idUserInterface* _hud ) { + if (GetVehicleInterfaceLocal()) { + GetVehicleInterfaceLocal()->DrawHUD( _hud ); + } +} + +/* +=============== +hhPlayer::FireWeapon +=============== +*/ +void hhPlayer::FireWeapon( void ) { + idMat3 axis; + idVec3 muzzle; + + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) { + return; + } + } + + //HUMANHEAD: aob - removed ammo check because we allow weapons to change modes + if( weapon.IsValid() ) { + AI_ATTACK_HELD = true; + weapon->BeginAttack(); + } + //HUMANEHAD END +} + +//========================================================================= +// +// hhPlayer::FireWeaponAlt +// +//========================================================================= + +void hhPlayer::FireWeaponAlt( void ) { + idMat3 axis; + idVec3 muzzle; + + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetBool() ) { + GetViewPos( muzzle, axis ); + if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], NULL ) ) { + return; + } + } + + if ( gameLocal.isMultiplayer && spectating ) { + static int lastSpectatorSwitch = 0; + if ( gameLocal.time > lastSpectatorSwitch + 500 ) { + spectator = gameLocal.GetNextClientNum( spectator ); + idPlayer *player = gameLocal.GetClientByNum( spectator ); + while ( player->spectating && player != this ) { + player = gameLocal.GetClientByNum(spectator); + } + lastSpectatorSwitch = gameLocal.time; + } + return; + } + + //HUMANHEAD: aob - removed ammo check because we allow weapons to change modes + if( weapon.IsValid() ) { + AI_ATTACK_HELD = true; + weapon->BeginAltAttack(); + } + //HUMANEHAD END + + if ( hud ) { + hud->HandleNamedEvent( "closeObjective" ); + } +} + +void hhPlayer::StopFiring( void ) { + idPlayer::StopFiring(); + if ( weapon.GetEntity() && weapon->IsType( hhWeaponRifle::Type ) ) { + static_cast( weapon.GetEntity() )->ZoomOut(); + } +} + +/* +=============== +hhPlayer::HasAmmo +=============== +*/ +int hhPlayer::HasAmmo( ammo_t type, int amount ) { + return inventory.HasAmmo( type, amount ); +} + +/* +=============== +hhPlayer::UseAmmo +=============== +*/ +bool hhPlayer::UseAmmo( ammo_t type, int amount ) { + return inventory.UseAmmo( type, amount ); +} + +/* +=============== +hhPlayer::NextBestWeapon +=============== +*/ +void hhPlayer::NextBestWeapon( void ) { + if ( ActiveGui() || IsSpiritOrDeathwalking() ) { + return; + } + idPlayer::NextBestWeapon(); +} + +/* +=============== +hhPlayer::SelectWeapon +=============== +*/ +void hhPlayer::SelectWeapon( int num, bool force ) { + if ( ! ( weaponFlags & ( 1 << ( num - 1 ) ) ) ) { + return; + } + if ( bFrozen || ActiveGui() || ( SkipWeapon(num) || IsSpiritOrDeathwalking() ) ) { + return; + } + idPlayer::SelectWeapon( num, force ); +} + +/* +=============== +hhPlayer::NextWeapon +=============== +*/ +void hhPlayer::NextWeapon( void ) { + if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) { + // NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key + return; + } + //idPlayer::NextWeapon(); + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + const char *weap; + int w, start; + + w = idealWeapon; + start = w; + while( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + // No weapon selected and nothing to select + if ( start == -1 ) { + idealWeapon = 0; + currentWeapon = 0; + return; + } + w = 0; + } + // Keep us from an infinite loop if no weapons are valid + if ( w == start ) { + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + idealWeapon = 0; + currentWeapon = 0; + } + return; + } + 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; + } + // Make sure the weapon is valid for this level + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + continue; + } + if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +hhPlayer::PrevWeapon +=============== +*/ +void hhPlayer::PrevWeapon( void ) { + if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) { + // NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key + return; + } + //idPlayer::PrevWeapon(); + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + const char *weap; + int w = idealWeapon, start = w; + if (w == -1) { + w = MAX_WEAPONS - 1; + } + while( 1 ) { + w--; + if ( w < 0 ) { + // No weapon selected and nothing to select + if ( start == -1 ) { + idealWeapon = 0; + currentWeapon = 0; + return; + } + + w = MAX_WEAPONS - 1; + } + // Keep us from an infinite loop if no weapons are valid + if ( w == start ) { + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + idealWeapon = 0; + currentWeapon = 0; + } + return; + } + 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; + } + // Make sure the weapon is valid for this level + if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) { + continue; + } + if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +hhPlayer::SkipWeapon +=============== +*/ +bool hhPlayer::SkipWeapon( int weaponNum ) const { + //No bow if not in spirit mode + return !IsSpiritOrDeathwalking() && weaponNum == spawnArgs.GetInt("spirit_weapon"); +} + +/* +=============== +hhPlayer::SnapDownCurrentWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::SnapDownCurrentWeapon() { + if( weapon.IsValid() ) { + weapon->SnapDown(); + } +} + +/* +=============== +hhPlayer::SnapUpCurrentWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::SnapUpCurrentWeapon() { + if( weapon.IsValid() ) { + weapon->SnapUp(); + } +} + +/* +=============== +hhPlayer::SelectEtherealWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::SelectEtherealWeapon() { + if ( GetCurrentWeapon() != 0 ) { + lastWeaponSpirit = GetCurrentWeapon(); + } + + weaponHandState.SetPlayer( this ); + if (!gameLocal.isClient) { + int spiritWeaponIndex = spawnArgs.GetInt("spirit_weapon"); + const char *spiritWeaponName = NULL; + if (bDeathWalk || (inventory.weapons & (1 << spiritWeaponIndex)) ) { + spiritWeaponName = GetWeaponName(spiritWeaponIndex); + } + weaponHandState.Archive( spiritWeaponName, 0, (InGUIMode()) ? "guihand_normal" : NULL ); + } + + if ( weapon.IsValid() ) { + weapon->SetShaderParm( SHADERPARM_MODE, MS2SEC( gameLocal.time ) ); // Glow in the bow + weapon->SetShaderParm( SHADERPARM_MISC, 1.0f ); // Turn on the arrow + weapon->SetShaderParm( SHADERPARM_DIVERSITY, MS2SEC( gameLocal.time ) ); // Glow in the arrow + } +} + +/* +=============== +hhPlayer::PutawayEtherealWeapon + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::PutawayEtherealWeapon() { + //If we haven't actually changed weapons yet, put current weapon up + if( GetCurrentWeapon() != lastWeaponSpirit ) { + SnapDownCurrentWeapon(); + } else { + SnapUpCurrentWeapon(); + } + + weaponHandState.RestoreFromArchive(); +} + +/* +=============== +hhPlayer::UpdateWeapon + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::UpdateWeapon( void ) { + + if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + return; + } + + assert( !spectating ); + + if ( gameLocal.isClient ) { + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( !weapon.GetEntity()->IsWorldModelReady() ) { + return; + } + } + else if (gameLocal.isMultiplayer) { //rww - projectile deferring + if (weapon.IsValid()) { + weapon->CheckDeferredProjectiles(); + } + } + + // always make sure the weapon is correctly setup before accessing it + if ( weapon.GetEntity() && !weapon.GetEntity()->IsLinked() ) { + if ( idealWeapon != -1 ) { + animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) ); + weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ idealWeapon ] ); + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD rww + assert( weapon.GetEntity()->IsLinked() ); + } else { + return; + } + } + + //HUMANEHAD rww + if (weapon.IsValid() && weapon->IsType(hhWeaponSoulStripper::Type)) { + hhWeaponSoulStripper *leechGun = static_cast(weapon.GetEntity()); + leechGun->CheckCans(); + } + //HUMANHEAD END + + if (!InVehicle()) { + if ( g_dragEntity.GetBool() ) { + StopFiring(); + dragEntity.Update( this ); + if ( weapon.IsValid() ) { + weapon.GetEntity()->FreeModelDef(); + } + return; + } else if (ActiveGui()) { + // gui handling overrides weapon use + Weapon_GUI(); + } else { + Weapon_Combat(); + } + + // Determine whether we are in aside state + if ( weapon.IsValid() && weapon->IsAside() ) { + if ( !AI_ASIDE ) { + AI_ASIDE = true; + SetState( "AsideWeapon" ); + UpdateScript(); + } + } else { + AI_ASIDE = false; + } + } + + //HUMANHEAD: aob - added weapon validity check + if( weapon.IsValid() ) { + // update weapon state, particles, dlights, etc + weapon->PresentWeapon( showWeaponViewModel ); + } + + // nla + if ( hand.IsValid() ) { + hand->Present(); + } +} + +/* +=============== +hhPlayer::InGUIMode +nla - Used for the GUI hand to check if it should be up. (Coming back from spiritwalk) +=============== +*/ +bool hhPlayer::InGUIMode() { + + // Logic adapted from UpdateWeapon + if ( ActiveGui() && !InVehicle() ) { + return( true ); + } + + return( false ); +} + + +/* +=============== +hhPlayer::Weapon_Combat + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::Weapon_Combat( void ) { + idMat3 axis; + idVec3 muzzle; + idDict args; + + if ( !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { + return; + } + + // HUMANHEAD nla + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && hand->IsAttached() && !hand->IsLowering() ) { + hand->RemoveHand(); + } + // HUMANHEAD END + + if( idealWeapon != 0 && IsLocked(idealWeapon) ) { //HUMANHEAD bjk PCF (4-30-06) - fix wrench up in roadhouse + NextWeapon(); + } + + if ( idealWeapon == 0 ) { + return; + } + + RaiseWeapon(); + if ( weapon.IsValid() ) { + weapon.GetEntity()->PutUpright(); + } + + if ( weapon.IsValid() && weapon->IsReloading() ) { + if ( !AI_RELOAD ) { + AI_RELOAD = true; + SetState( "ReloadWeapon" ); + UpdateScript(); + } + } else { + AI_RELOAD = false; + } + + if ( idealWeapon != currentWeapon && (!gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time) ) { //HUMANHEAD bjk PATCH 7-27-06 + if ( weaponCatchup ) { + assert( gameLocal.isClient ); +#ifndef HUMANHEAD //HUMANHEAD rww FIXME!!!! this also produces horrible memory leaks because of the dirty fire controller stuff + //HUMANHEAD rww - our crazy weapon system does not work ok with this. did we change the weapon dictionary parsing? + //it seems that it is very much dependant on the base class of weapon at the moment and that of course is going to be + //that of currentWeapon and not idealWeapon. + //currentWeapon = idealWeapon; + //HUMANHEAD END + weaponGone = false; + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] ); + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention + + weapon.GetEntity()->NetCatchup(); + const function_t *newstate = GetScriptFunction( "NetCatchup" ); + if ( newstate ) { + SetState( newstate ); + UpdateScript(); + } +#else + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) ); + const char *currentWeaponName = weapon->spawnArgs.GetString("classname", ""); + //make sure the weapon i have from my snapshot, and the weapon i have selected are the same. + if (currentWeaponName && !idStr::Cmp(animPrefix, currentWeaponName)) { + weaponGone = false; + currentWeapon = idealWeapon; + + weapon->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] ); + animPrefix.Strip( "weaponobj_" ); + weapon->Raise(); +#endif //HUMANHEAD END + weaponCatchup = false; + } + } else { + if ( weapon.IsValid() && (weapon->IsReady() || weapon->IsRising()) ) { + InvalidateCurrentWeapon();//Needed incase we change weapons quickly, we can go back to old weapon + weapon->PutAway(); + } + + if ( ( !weapon.IsValid() || weapon->IsHolstered() ) && !bDeathWalk && ! bReallyDead ) { + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + if ( currentWeapon > 0 && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) { //HUMANHEAD bjk + previousWeapon = currentWeapon; + } + //HUMANHEAD PCF rww 05/03/06 - the local client might get skippiness between switching weapons if we + //attempt to raise the weapon again before validating that the weapon ent is of the type that we + //already desire to switch to. this bug is introduced by the concept of switching out entities when + //changing weapons (not client-friendly). + if (gameLocal.isClient && entityNumber == gameLocal.localClientNum) { + if (weapon.IsValid() && weapon->GetDict() && idStr::Icmp(weapon->GetDict()->GetString("classname"), GetWeaponName( idealWeapon )) == 0) { + currentWeapon = idealWeapon; + weaponGone = false; + animPrefix = GetWeaponName( currentWeapon ); + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention + weapon.GetEntity()->Raise(); + } + } + //HUMANHEAD END + else { + currentWeapon = idealWeapon; + weaponGone = false; + + //HUMANHEAD: aob + animPrefix = GetWeaponName( currentWeapon ); + if (!gameLocal.isClient) { + SAFE_REMOVE( weapon ); + weapon = SpawnWeapon( animPrefix.c_str() ); + } + //HUMANHEAD END + + animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention + + //HUMANHEAD PCF rww 05/03/06 - safety check, make sure weapon is valid particularly for the client + if (weapon.IsValid()) { + weapon.GetEntity()->Raise(); + } + } + } + } + } else if (!bDeathWalk && !bReallyDead) { + weaponGone = false; // if you drop and re-get weap, you may miss the = false above + if ( weapon.IsValid() && weapon.GetEntity()->IsHolstered() ) { + if ( !weapon.GetEntity()->AmmoAvailable() ) { + // weapons can switch automatically if they have no more ammo + NextBestWeapon(); + } else if( !gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time ) { //HUMANHEAD bjk PATCH 9-11-06 + weapon.GetEntity()->Raise(); + state = GetScriptFunction( "RaiseWeapon" ); + if ( state ) { + SetState( state ); + } + } + } + } + + if ( weapon.IsValid() ) { + weapon->PrecomputeTraceInfo(); + } + + // check for attack + AI_WEAPON_FIRED = false; + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { + FireWeapon(); + } else if ( oldButtons & BUTTON_ATTACK ) { + AI_ATTACK_HELD = false; + if( weapon.IsValid() ) { + weapon.GetEntity()->EndAttack(); + } + } + + // HUMANHEAD + if ( ( usercmd.buttons & BUTTON_ATTACK_ALT ) && !weaponGone ) { + FireWeaponAlt(); + } else if ( oldButtons & BUTTON_ATTACK_ALT ) { + AI_ATTACK_HELD = false; + if( weapon.IsValid() ) { + weapon.GetEntity()->EndAltAttack(); + } + } + // HUMANHEAD END + + // update our ammo clip in our inventory + if ( weapon.IsValid() && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.clip[ currentWeapon ] = weapon->AmmoInClip(); + inventory.altMode[ currentWeapon ] = weapon->GetAltMode(); + } +} + +/* +=============== +hhPlayer::SpawnWeapon +=============== +*/ +hhWeapon* hhPlayer::SpawnWeapon( const char* name ) { + hhWeapon* weaponPtr = NULL; + idDict args; + + args.SetVector( "origin", GetEyePosition() ); + args.SetMatrix( "rotation", viewAngles.ToMat3() ); + weaponPtr = static_cast( gameLocal.SpawnObject(name, &args) ); + weaponPtr->SetOwner( this ); + weaponPtr->Bind( this, true ); + + //HUMANHEAD: aob - removed last two params + weaponPtr->GetWeaponDef( name ); + + return weaponPtr; +} + +/* +===================== +hhPlayer::GetWeaponNum + HUMANHEAD +===================== +*/ +int hhPlayer::GetWeaponNum( const char* weaponName ) const { + for( int i = 1; i < MAX_WEAPONS; ++i ) { + if( !idStr::Icmp(GetWeaponName(i), weaponName) ) { + return i; + } + } + + return 0; +} + +/* +===================== +hhPlayer::GetWeaponName + HUMANHEAD +===================== +*/ +const char* hhPlayer::GetWeaponName( int num ) const { + if( num < 1 || num >= MAX_WEAPONS ) { + return NULL; + } + + return spawnArgs.GetString( va("def_weapon%d", num) ); +} + +/* +=============== +hhPlayer::Weapon_GUI + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::Weapon_GUI( void ) { + + StopFiring(); + weapon.GetEntity()->LowerWeapon(); + + // NLANOTE - Same here + // disable click prediction for the GUIs. handy to check the state sync does the right thing + if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) { + return; + } + + if ( health <= 0 || bDeathWalk ) { //HUMANHEAD bjk PCF (5-5-06) - no hand stuff when dead + return; + } + + // HUMANHEAD nla + if ( !bDeathWalk ) { // mdl - Don't do hand stuff while deathwalking + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) ) { + //if ( !hand->IsReady() && !hand->IsRaising() ) { + if ( !hand->IsReady() && !hand->IsRaising() && !hand->IsLowering() ) { + hand->Reraise(); + } + } + else if ( (!hand.IsValid() && weapon.IsValid() && !weapon->IsAside() ) || + (hand.IsValid() && !hand->IsType( hhGuiHand::Type ) && !hand->IsLowering() ) || + (!hand.IsValid() && idealWeapon == 0 ) ) { + if (!gameLocal.isClient) { //rww + hhHand::AddHand( this, GetGuiHandInfo() ); + } + } + } + // HUMANHEAD END + + if ( hand.IsValid() && ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { //HUMANHEAD bjk: no waiting for ready + 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 && focusGUIent && ui == focusUI ) { + focusGUIent->UpdateVisuals(); + } + } + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + if (focusGUIent) { + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) { //rww - still predict hand + hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk + hand->Action(); + } + } + return; + } + if ( focusGUIent ) { + HandleGuiCommands( focusGUIent, command ); + // HUMANHEAD nla - Added to handle the hand stuff. = ) + if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) { + hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk + hand->Action(); + } + // HUMANHEAD END + } else { + HandleGuiCommands( this, command ); + } + } +} + +/* +================ +hhPlayer::UpdateCrosshairs +HUMANHEAD PDM +================ +*/ +void hhPlayer::UpdateCrosshairs() { + bool combatCrosshair = false; + bool targeting = false; + int crosshair = 0; + + if ( !privateCameraView && !IsLocked(idealWeapon) && (!hand.IsValid() || hand->IsLowered()) && !InCinematic() && weapon.IsValid() && g_crosshair.GetInteger() ) { + weapon->UpdateCrosshairs(combatCrosshair, targeting); + crosshair = g_crosshair.GetInteger(); + } + + cursor->SetStateBool( "combatcursor", targeting ? false : combatCrosshair ); + cursor->SetStateBool( "activecombatcursor", targeting ); + cursor->SetStateInt( "crosshair", crosshair ); +} + + +/* +================ +hhPlayer::UpdateFocus + +Searches nearby entities for interactive guis, possibly making one of them +the focus and sending it a mouse move event + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================ +*/ +void hhPlayer::UpdateFocus( void ) { + idClipModel *clipModelList[ MAX_GENTITIES ]; + idClipModel *clip; + int listedClipModels; + idEntity *oldFocus; + idEntity *ent; + idUserInterface *oldUI; + int i, j; + idVec3 start, end; + const char *command; + trace_t trace; + guiPoint_t pt; + const idKeyValue *kv; + sysEvent_t ev; + idUserInterface *ui; + + if ( gameLocal.inCinematic ) { + return; + } + + oldFocus = focusGUIent; + oldUI = focusUI; + + if ( focusTime <= gameLocal.time ) { + ClearFocus(); + } + + // don't let spectators interact with GUIs + if ( spectating ) { + return; + } + + start = GetEyePosition(); + + //HUMANHEAD rww - the actual viewAngles do not seem to be properly transformed to at this point, + //so just get a transformed direction now. + idVec3 viewDir = (untransformedViewAngles.ToMat3()*GetEyeAxis())[0]; + + end = start + viewDir * 70.0f; // was 50, doom=80 + + // HUMANHEAD pdm: Changed aim logic a bit + // player identification -> names to the hud + if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) { + idVec3 end = start + viewDir * 768.0f; + gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) { + MPAim = trace.c.entityNum; + } + else { + MPAim = -1; + + //HUMANHEAD rww - if we're hitting a shuttle with a pilot, set the pilot to the MPAim ent + if (trace.c.entityNum >= MAX_CLIENTS && trace.c.entityNum < MAX_GENTITIES) { + idEntity *traceEnt = gameLocal.entities[trace.c.entityNum]; + if (traceEnt && traceEnt->IsType(hhVehicle::Type)) { //is it a vehicle? + hhVehicle *traceVeh = static_cast(traceEnt); + if (traceVeh->GetPilot() && traceVeh->GetPilot()->IsType(hhPlayer::Type)) { //if it's a vehicle, does it have a player pilot? + MPAim = traceVeh->GetPilot()->entityNumber; + } + } + } + //HUMANHEAD END + } + } + + idBounds bounds( start ); + bounds.AddPoint( end ); + + listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + // no pretense at sorting here, just assume that there will only be one active + // gui within range along the trace + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + // HUMANHEAD pdm: added support here for all guis, not just gui1 + int interactiveMask = 0; + renderEntity_t *renderEnt = ent->GetRenderEntity(); + if ( renderEnt ) { + for (int ix=0; ixgui[ix] && renderEnt->gui[ix]->IsInteractive()) { + interactiveMask |= (1<GuiTrace( ent->GetModelDefHandle(), start, end/*, interactiveMask*/); // jmarshall implement interactiveMask + + if ( ent->fl.accurateGuiTrace ) { + trace_t tr; + gameLocal.clip.TracePoint(tr, start, end, CONTENTS_SOLID, this); + if (tr.fraction < pt.frac) { + continue; + } + } + + if ( pt.x != -1 ) { + // we have a hit + renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity(); + if ( !focusGUIrenderEntity ) { + continue; + } + + if ( pt.guiId == 1 ) { + ui = focusGUIrenderEntity->gui[ 0 ]; + } else if ( pt.guiId == 2 ) { + ui = focusGUIrenderEntity->gui[ 1 ]; + } else { + ui = focusGUIrenderEntity->gui[ 2 ]; + } + + if ( ui == NULL ) { + continue; + } + + ClearFocus(); + focusGUIent = ent; + focusUI = ui; + + if ( oldFocus != ent ) { + // new activation + // going to see if we have anything in inventory a gui might be interested in + // need to enumerate inventory items + focusUI->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" ); + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + + focusUI->SetStateString( va( "inv_name_%i", j), iname ); + focusUI->SetStateString( va( "inv_icon_%i", j), iicon ); + focusUI->SetStateString( va( "inv_text_%i", j), itext ); + kv = item->MatchPrefix("inv_id", NULL); + if ( kv ) { + focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + // HUMANHEAD nla - Changed to pass all "passtogui_" keys to the gui + kv = item->MatchPrefix("passtogui_", NULL); + if ( kv ) { + focusUI->SetStateString( kv->GetKey(), kv->GetValue() ); + kv = item->MatchPrefix( "passtogui_", kv ); + } + // HUMANHEAD END + focusUI->SetStateInt( iname, 1 ); + } + + focusUI->SetStateString( "player_health", va("%i", health ) ); + focusUI->SetStateBool( "player_spiritwalking", IsSpiritWalking() ); // for hunterhand gui + + kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL ); + while ( kv ) { + focusUI->SetStateString( kv->GetKey(), kv->GetValue() ); + kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv ); + } + } + + // clamp the mouse to the corner + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = focusUI->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + + // move to an absolute position + ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); + command = focusUI->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + focusTime = gameLocal.time + FOCUS_GUI_TIME; + break; + } + } + + if ( focusGUIent && focusUI ) { + if ( !oldFocus || oldFocus != focusGUIent ) { + command = focusUI->Activate( true, gameLocal.time ); + HandleGuiCommands( focusGUIent, command ); + //StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else if ( oldFocus && oldUI ) { + command = oldUI->Activate( false, gameLocal.time ); + HandleGuiCommands( oldFocus, command ); + //StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + + +/* +=============== +hhPlayer::ApplyLandDeflect + +HUMANHEAD: aob +=============== +*/ +idVec3 hhPlayer::ApplyLandDeflect( const idVec3& pos, float scale ) { + idVec3 localPos( pos ); + int delta = 0; + float fraction = 0.0f; + + // add fall height + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + fraction = (float)delta / LAND_DEFLECT_TIME; + //HUMANHEAD: aob + fraction *= scale; + //HUMANHEAD END + localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + fraction = (float)(LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + //HUMANHEAD: aob + fraction *= scale; + //HUMANHEAD END + localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction; + } + + return localPos; +} + +/* +================= +hhPlayer::CrashLand + +Check for hard landings that generate sound events + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +================= +*/ +void hhPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + //HUMANHEAD: aob - removed unused vars + idVec3 origin; + idVec3 gravityNormal; + float delta; + waterLevel_t waterLevel; + bool noDamage; + //HUMANHEAD END + + //HUMANHEAD: aob + const trace_t& trace = physicsObj.GetGroundTrace(); + //HUMANHEAD END + + AI_SOFTLANDING = false; + AI_HARDLANDING = false; + + //HUMANHEAD: aob - added IsBound and trace check + // if the player is not on the ground + if ( (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { + return; + } + + //HUMANHEAD: aob - only check when we land on the ground + //If we get here we can assume we currently have ground contacts + if( physicsObj.HadGroundContacts() ) { + return; + } + //HUMANHEAD END + + 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; + } + } + + //HUMANHEAD: aob - removed velocity calculations + + //HUMANHEAD: aob + idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); + delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * gravityNormal; + //HUMANHEAD END + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < crashlandSpeed_jump || IsSpiritOrDeathwalking() ) { + return; // Early out + } + + // Calculate landing sound volume + float soundScale = hhUtils::CalculateScale( delta, crashlandSpeed_jump, crashlandSpeed_fatal ); + landChange = -32 * soundScale; + landTime = gameLocal.time; + float min = hhMath::dB2Scale( spawnArgs.GetInt("minCrashLandVolumedB", "-20") ); + float max = hhMath::dB2Scale( spawnArgs.GetInt("maxCrashLandVolumedB", "-4") ); + soundScale = min + (max - min) * soundScale; + + idVec3 fallDir = oldVelocity; + idVec3 reverseContactNormal = -physicsObj.GetGroundContactNormal(); + idEntity* entity = NULL; + float damageScale = hhUtils::CalculateScale( delta, crashlandSpeed_soft, crashlandSpeed_fatal ); + + fallDir.Normalize(); + + if( trace.fraction == 1.0f ) { + return; + } + + if( !IsBound() ) { + PlayCrashLandSound( trace, soundScale ); + gameLocal.AlertAI( this, spawnArgs.GetFloat( "land_alert_radius", "400" ) ); + } + + // Determine damage to what you're landing on + entity = gameLocal.GetTraceEntity( trace ); + if( entity && trace.c.entityNum != ENTITYNUM_WORLD ) { + entity->ApplyImpulse( this, 0, trace.c.point, (oldVelocity * reverseContactNormal) * reverseContactNormal );//Not sure if this impulse is large enough + + const char* entityDamageName = spawnArgs.GetString( "def_damageFellOnto" ); + if( *entityDamageName && damageScale > 0.0f) { + entity->Damage( this, this, fallDir, entityDamageName, damageScale, INVALID_JOINT ); + } + } + + // Calculate damage to self + const char* selfDamageName = NULL; + if ( delta < crashlandSpeed_soft ) { // Soft Fall + AI_SOFTLANDING = true; + selfDamageName = spawnArgs.GetString( "def_damageSoftFall" ); + } + else if ( delta < crashlandSpeed_fatal ) { // Hard Fall + AI_HARDLANDING = true; + selfDamageName = spawnArgs.GetString( "def_damageHardFall" ); + } + else { // Fatal Fall + AI_HARDLANDING = true; + selfDamageName = spawnArgs.GetString( "def_damageFatalFall" ); + } + + if( *selfDamageName && damageScale > 0.0f && !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, fallDir, selfDamageName, damageScale, INVALID_JOINT ); + } +} + +/* +=============== +hhPlayer::ApplyBobCycle + +HUMANHEAD: aob +=============== +*/ +#define BOB_TO_NOBOB_RETURN_TIME 3000.0f +idVec3 hhPlayer::ApplyBobCycle( const idVec3& pos, const idVec3& velocity ) { + float delta = 0.0f; + idVec3 localViewBob( pos ); + const float maxBob = 6.0f; + + if( bob > 0.0f && !usercmd.forwardmove && !usercmd.rightmove ) {//smoothly goto bob of zero if not already there + delta = gameLocal.time - lastAppliedBobCycle; + bob *= ( 1.0f - (delta / BOB_TO_NOBOB_RETURN_TIME) ); + if( bob < 0.0f ) { + bob = 0.0f; + } + } else { + // add bob height after any movement smoothing + lastAppliedBobCycle = gameLocal.time; + bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); + if( bob > maxBob ) { + bob = maxBob; + } + } + + localViewBob -= cameraInterpolator.GetCurrentUpVector() * bob; + + return localViewBob; +} + + +/* +=============== +hhPlayer::BobCycle + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::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; + + //HUMANHEAD: aob - changed GetGravityNormal to GetCurrentUpVector to smooth out wallwalk + gravityDir = -cameraInterpolator.GetCurrentUpVector(); + //HUMANHEAD END + vel = velocity - ( velocity * gravityDir ) * gravityDir; + xyspeed = vel.LengthFast(); + + // do not evaluate the bob for other clients + // when doing a spectate follow, don't do any weapon bobbing + if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) { + //HUMANHEAD rww - allow bob when following players in spectator mode + bool canBob = false; + if (gameLocal.localClientNum != -1 && gameLocal.entities[gameLocal.localClientNum] && gameLocal.entities[gameLocal.localClientNum]->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(gameLocal.entities[gameLocal.localClientNum]); + if (pl->spectating && pl->spectator == entityNumber) { + canBob = true; + } + } + + if (!canBob) { + //HUMANHEAD END + viewBobAngles.Zero(); + viewBob.Zero(); + return; + } + } + + 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.msec ) & 255; + bobFoot = ( bobCycle & 128 ) >> 7; + bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); + } + + // calculate angles for view bobbing + viewBobAngles.Zero(); + + viewaxis = viewAngles.ToMat3(); + + // 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; + + //HUMANHEAD: aob +#if HUMANHEAD + viewBob = ApplyBobCycle( ApplyLandDeflect(vec3_zero, 1.0f), velocity ); +#else + // 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; + } + viewBob[2] += bob; + + // 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 ); + } +#endif //HUMANHEAD END +} + +/* +================ +hhPlayer::UpdateDeltaViewAngles +================ +*/ +void hhPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { + // set the delta angle + idAngles delta; + for( int i = 0; i < 3; i++ ) { +#if 1 //rww - revert to id's method + //delta[i] = ((angles[i] - GetUntransformedViewAngles()[i]) / GetViewAnglesSensitivity()) + GetUntransformedViewAngles()[i] - SHORT2ANGLE(usercmd.angles[i]); + delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); +#else // HUMANHEAD mdl: If you enable this, comment out the SetOrientation() call for vehicles in hhPlayer::Restore() + delta[i] = (angles[i] - idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[ i ]) - oldCmdAngles[i]) * GetViewAnglesSensitivity()); +#endif + } + SetDeltaViewAngles( delta ); +} + +/* +================ +hhPlayer::DetermineViewAngles +================ +*/ +idAngles hhPlayer::DetermineViewAngles( const usercmd_t& cmd, idAngles& cmdAngles ) { + int i; + idAngles localViewAngles( GetUntransformedViewAngles() ); + idAngles deltaCmdAngles; + + 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 + return GetUntransformedViewAngles(); + } + + //HUMANHEAD rww - testing + /* + if (gameLoacl.isClient && gameLocal.localClientNum != entityNumber) { + idQuat a = viewAngles.ToQuat(); + idQuat b = GetUntransformedViewAngles().ToQuat(); + idQuat c; + c.Slerp(a, b, 0.3f); + return c.ToAngles(); + } + */ + //HUMANHEAD END + + // if dead + if ( IsDead() ) { +#if 0 + if ( DoThirdPersonDeath() ) { + localViewAngles.roll = 0.0f; + localViewAngles.pitch = 30.0f; + } + else { + localViewAngles.roll = 40.0f; + localViewAngles.pitch = -15.0f; + } + return localViewAngles; +#else //HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP + if ( !DoThirdPersonDeath() ) { + localViewAngles.roll = 40.0f; + localViewAngles.pitch = -15.0f; + return localViewAngles; + } +#endif //HUMANHEAD END + } + + //JSHTODO this messes up multiplayer input. remerge sensitivity code + // circularly clamp the angles with deltas + if ( usercmd.buttons & BUTTON_MLOOK ) { + for ( i = 0; i < 3; i++ ) { + cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); +#if 1 //rww - revert to id's method + //localViewAngles[i] = idMath::AngleNormalize180( cmdAngles[i] + deltaViewAngles[i] - GetUntransformedViewAngles()[i] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[i]; + localViewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ); +#else + deltaCmdAngles[i] = idMath::AngleNormalize180( cmdAngles[i] - oldCmdAngles[i] ) * GetViewAnglesSensitivity(); + oldCmdAngles[i] = cmdAngles[i]; + localViewAngles[i] = deltaCmdAngles[i] + deltaViewAngles[i]; +#endif + } + } else { +#if 1 //rww - revert to id's method + //localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW] ) + deltaViewAngles[YAW] - GetUntransformedViewAngles()[YAW] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[YAW]; + localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW]) + deltaViewAngles[YAW] ); +#if GAMEPAD_SUPPORT // VENOM BEGIN + oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] ); +#endif // VENOM END +#else + deltaCmdAngles[YAW] = idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[YAW]) - oldCmdAngles[YAW] ) * GetViewAnglesSensitivity(); + oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] ); + localViewAngles.yaw = deltaCmdAngles[YAW] + deltaViewAngles[YAW]; +#endif + + if (!centerView.IsDone(gameLocal.time)) { + localViewAngles.pitch = centerView.GetCurrentValue(gameLocal.time); + } + } + +#if GAMEPAD_SUPPORT // VENOM BEGIN + float fpitch = idMath::Fabs(localViewAngles.pitch); + if( idMath::Abs(usercmd.rightmove) < 10 && + idMath::Abs(usercmd.forwardmove) > 80 && + fpitch > 1.f && + idMath::Fabs(oldCmdAngles[YAW] - cmdAngles[YAW]) < 1.0f && + idMath::Fabs(oldCmdAngles[PITCH] - cmdAngles[PITCH]) < 1.0f + ) + { + // dont start the leveling until we have .5 sec of valid movement + if((gameLocal.time - lastAutoLevelTime) > 500 ) { + float fadd = 0.6f; + if(fpitch < 10.f) { + fadd *= (fpitch/10.f ); + fadd+= 0.05f; + } + + if(localViewAngles.pitch < 0 ) { + localViewAngles.pitch +=fadd; + } + else { + localViewAngles.pitch -=fadd; + } + } + } + else { + lastAutoLevelTime = gameLocal.time; + } + + oldCmdAngles[YAW] = cmdAngles[YAW]; + oldCmdAngles[PITCH] = cmdAngles[PITCH]; +#endif // VENOM END + + // clamp the pitch + if ( noclip ) { + localViewAngles.pitch = hhMath::ClampFloat( -89.0f, 89.0f, localViewAngles.pitch ); + } else { + //HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP + if (IsDead() && DoThirdPersonDeath()) { + localViewAngles.pitch = hhMath::ClampFloat( -45.0f, 45.0f, localViewAngles.pitch ); + } + else { + //HUMANHEAD END + localViewAngles.pitch = hhMath::ClampFloat( pm_minviewpitch.GetFloat(), pm_maxviewpitch.GetFloat(), localViewAngles.pitch ); + } + } + + // HUMANHEAD pdm: clamp the yaw (only used for bindControllers) + if ( !noclip && bClampYaw ) { + float idealYaw=0.0f; + idVec3 masterOrigin; + idMat3 masterAxis; + + if (GetMasterPosition(masterOrigin, masterAxis)) { + idealYaw = masterAxis[0].ToYaw(); + idealYaw = idMath::AngleNormalize180( idealYaw ); + } + + // Transpose the everything to "zero" space before clamping, so idealYaw is at zero in a (-180..180) normalized space + float zeroSpaceYaw = idMath::AngleNormalize180( localViewAngles.yaw - idealYaw ); + zeroSpaceYaw = idMath::ClampFloat(-maxRelativeYaw, maxRelativeYaw, zeroSpaceYaw); + localViewAngles.yaw = idMath::AngleNormalize180( zeroSpaceYaw + idealYaw ); + } + // HUMANHEAD END + + UpdateDeltaViewAngles( localViewAngles ); + + // save in the log for analyzing weapon angle offsets + loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = localViewAngles; + + return localViewAngles; +} + +/* +================ +hhPlayer::SetViewAnglesSensitivity +================ +*/ +void hhPlayer::SetViewAnglesSensitivity( float factor ) { + viewAnglesSensitivity = factor; + if (gameLocal.localClientNum == entityNumber) { + //common->SetGameSensitivityFactor(factor); // jmarshall + } +} + +/* +================ +hhPlayer::GetViewAnglesSensitivity +================ +*/ +float hhPlayer::GetViewAnglesSensitivity() const { + return viewAnglesSensitivity; +} + +/* +================ +hhPlayer::UpdateViewAngles +================ +*/ +void hhPlayer::UpdateViewAngles( void ) { + if( InVehicle() ) { + return; + } + + if (!noclip && (InCinematic() && lockView)) { + // 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; + } + + //HUMANHEAD: aob - moved logic into helper function so we can call this somewhere else while in a vehicle + viewAngles = DetermineViewAngles( usercmd, cmdAngles ); + + //HUMANHEAD rww - moved angle smoothing code here + // update the smoothed view angles + if (gameLocal.isClient) { + if ( gameLocal.framenum >= smoothedFrame && entityNumber != gameLocal.localClientNum ) { + idAngles anglesDiff = viewAngles - smoothedAngles; + anglesDiff.Normalize180(); + if ( idMath::Fabs( anglesDiff.yaw ) < 90.0f && idMath::Fabs( anglesDiff.pitch ) < 90.0f ) { + // smoothen by pushing back to the previous angles + viewAngles -= gameLocal.clientSmoothing * anglesDiff; + viewAngles.Normalize180(); + } + smoothedAngles = viewAngles; + } + smoothedOriginUpdated = false; + } + + // orient the model towards the direction we're looking + UpdateOrientation( viewAngles ); + //HUMANHEAD END +} + +/* +===================== +hhPlayer::UpdateOrientation + +HUMANHEAD: aob +===================== +*/ +void hhPlayer::UpdateOrientation( const idAngles& newUntransformedViewAngles ) { + idAngles angles; + //This is where the camera interpolator transforms the viewAngles to work with wallwalk. + //We also store the original untransformed view angles for use in some other code. + //idPlayer called SetAxis, but only used the yaw but because we are transforming our view angles + //we do something similier by taking the untransformed yaw and rotating by the camera interpolator's axis. + //This becomes our new axis, the camera interpolator axis plus the untransformed view angles yaw + SetUntransformedViewAngles( newUntransformedViewAngles ); + + angles = GetUntransformedViewAngles(); + if( IsWallWalking() ) { + angles.pitch = 0.0f; + } + SetUntransformedViewAxis( idAngles( 0.0f, angles.yaw, 0.0f ).ToMat3() ); + + viewAngles = cameraInterpolator.UpdateViewAngles( angles ); + SetAxis( TransformToPlayerSpace(GetUntransformedViewAxis()) ); +} + +//============================================================================= +// +// hhPlayer::CheckFOV +// +// Similar to idActor::CheckFOV, but isn't infinite along the vertical plane +// +// CJR +//============================================================================= + +bool hhPlayer::CheckFOV( const idVec3 &pos ) { + if ( fovDot == 1.0f ) { + return true; + } + + float dot; + idVec3 delta; + + delta = pos - GetEyePosition(); + delta.Normalize(); + + dot = delta * viewAngles.ToForward(); + + return ( dot >= fovDot ); +} + +//============================================================================= +// hhPlayer::CheckFOV +// jsh - Similar to CheckFOV, but only checks yaw +//============================================================================= +bool hhPlayer::CheckYawFOV( const idVec3 &pos ) { + if ( fovDot == 1.0f ) { + return true; + } + + float dot; + idVec3 delta; + idVec3 viewAng; + + delta = pos - GetEyePosition(); + delta.z = 0; + delta.Normalize(); + viewAng = viewAngles.ToForward(); + viewAng.z = 0; + dot = delta * viewAng; + + return ( dot >= fovDot ); +} + + +/* +===================== +hhPlayer::SetOrientation + +HUMANHEAD: aob +===================== +*/ +void hhPlayer::SetOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) { + //never let the untransformed view angles have a roll value + idAngles modifiedUntransViewAngles = newUntransformedViewAngles; + modifiedUntransViewAngles.roll = 0.0f; + + physicsObj.SetOrigin( GetLocalCoordinates(origin) ); + physicsObj.SetAxis( bboxAxis ); + physicsObj.CheckWallWalk( true ); + + SetViewAngles( modifiedUntransViewAngles ); + BufferLoggedViewAngles( modifiedUntransViewAngles ); + SetUntransformedViewAngles( modifiedUntransViewAngles ); + + SetAxis( lookDir.ToMat3() ); + SetUntransformedViewAxis( idAngles(0.0f, modifiedUntransViewAngles.yaw, 0.0f).ToMat3() ); + + cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() ); + + UpdateViewAngles(); + UpdateVisuals(); +} +/* +===================== +hhPlayer::RestoreOrientation + +HUMANHEAD: mdl +Same as SetOrientation, except doesn't check wallwalk, since the materials may not be set before the first frame. +===================== +*/ +void hhPlayer::RestoreOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) { + physicsObj.SetOrigin( GetLocalCoordinates(origin) ); + physicsObj.SetAxis( bboxAxis ); + + SetViewAngles( newUntransformedViewAngles ); + BufferLoggedViewAngles( newUntransformedViewAngles ); + SetUntransformedViewAngles( newUntransformedViewAngles ); + + SetAxis( lookDir.ToMat3() ); + SetUntransformedViewAxis( idAngles(0.0f, newUntransformedViewAngles.yaw, 0.0f).ToMat3() ); + cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() ); + + UpdateViewAngles(); + UpdateVisuals(); +} + +/* +===================== +hhPlayer::BufferLoggedViewAngles + +HUMANHEAD: aob - used when teleporting and using portals +===================== +*/ +void hhPlayer::BufferLoggedViewAngles( const idAngles& newUntransformedViewAngles ) { + idAngles deltaAngles = newUntransformedViewAngles - GetUntransformedViewAngles(); + + for( int ix = 0; ix < NUM_LOGGED_VIEW_ANGLES; ++ix ) { + loggedViewAngles[ix] += deltaAngles; + } +} + +/* +================ +hhPlayer::UpdateFromPhysics + +AOBMERGE - PERSISTANT MERGE +================ +*/ +void hhPlayer::UpdateFromPhysics( bool moveBack ) { + + // set master delta angles for actors + if ( GetBindMaster() ) { + if( !InVehicle() ) { + idAngles delta = GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= physicsObj.GetMasterDeltaYaw(); + } else { + delta.yaw += physicsObj.GetMasterDeltaYaw(); + } + + SetDeltaViewAngles( delta ); + } else { + SetUntransformedViewAxis( mat3_identity ); + SetUntransformedViewAngles( GetUntransformedViewAxis().ToAngles() ); + viewAngles = GetUntransformedViewAngles(); + SetAxis( GetBindMaster()->GetAxis() ); + + //AOB - now that we have an updated viewAxis we need to update the + //camera. Feels like a hack! + cameraInterpolator.SetTargetAxis( GetAxis(), INTERPOLATE_NONE ); + } + } + + UpdateVisuals(); +} + +/* +============== +hhPlayer::PerformImpulse +============== +*/ + +void hhPlayer::PerformImpulse( int impulse ) { + if( InVehicle() && GetVehicleInterface()->InvalidVehicleImpulse(impulse) ) { + return; + } + + idPlayer::PerformImpulse( impulse ); + + switch( impulse ) { + case IMPULSE_14: { + if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) { + hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); + if ( weap && weap->GetAltMode() ) { + weap->ZoomInStep(); + } else { + NextWeapon(); + } + } else { + NextWeapon(); + } + break; + } + case IMPULSE_15: { + if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) { + hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); + if ( weap && weap->GetAltMode() ) { + weap->ZoomOutStep(); + } else { + PrevWeapon(); + } + } else { + PrevWeapon(); + } + break; + } + case IMPULSE_16: + // Toggle Lighter + if (inventory.requirements.bCanUseLighter && weaponFlags != 0) { // mdl: Disable lighter if all weapons are disabled + if (!gameLocal.isClient) { + ToggleLighter(); + } + } + break; + case IMPULSE_25: + // Throw grenade + if ( weaponFlags != 0 && !ActiveGui()) { // mdl: Disable if all weapons are disabled + if (!gameLocal.isClient) { + ThrowGrenade(); + } + } + break; + case IMPULSE_54: + // Spirit Power key + if ( IsDeathWalking() ) { // CJR: Developer-only ability to quickly return from DeathWalk by hitting the spirit key + if ( developer.GetBool() && ( gameLocal.time - deathWalkTime > spawnArgs.GetInt( "deathWalkMinTime", "4000" ) ) ) { // Force the player to stay in deathwalk for a short period of time + deathWalkFlash = 0.0f; + PostEventMS( &EV_PrepareToResurrect, 0 ); + } + } + else if (inventory.requirements.bCanSpiritWalk) { + ToggleSpiritWalk(); + } + break; + + case IMPULSE_18: { +#if GAMEPAD_SUPPORT // VENOM BEGIN + idAngles localViewAngles( GetUntransformedViewAngles() ); + centerView.Init(gameLocal.time, 360, localViewAngles.pitch, 0); +#else + centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); +#endif // VENOM END + break; + } + } +} + +void hhPlayer::Present() { + idPlayer::Present(); + + if ( lighterHandle != -1 ) { + // Update oscillation position + idVec3 oscillation; + oscillation.x = 0.0f; + oscillation.y = idMath::Cos( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_yspeed") ) * spawnArgs.GetFloat("lighter_ysize"); + oscillation.z = idMath::Sin( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_zspeed") ) * spawnArgs.GetFloat("lighter_zsize"); + + idVec3 offset; + offset = spawnArgs.GetVector("offset_lighter"); + offset.z = EyeHeight(); + lighter.origin = GetOrigin() + GetAxis() * (offset + oscillation); + lighter.axis = mat3_identity; + + lighter.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ]; + + gameRenderWorld->UpdateLightDef( lighterHandle, &lighter ); + } +} + +void hhPlayer::LighterOn() { + if (IsSpiritOrDeathwalking() || InVehicle() || spectating || bReallyDead) { // No lighter in spirit mode, deathwalk, or when in vehicles (or when spectating or really dead -rww) + return; + } + + if (lighterHandle == -1) { + // Add the dynamic light + memset( &lighter, 0, sizeof( lighter ) ); + lighter.lightId = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + lighter.pointLight = true; // false; ? + lighter.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_lighter" ) ); + lighter.shaderParms[ SHADERPARM_RED ] = spawnArgs.GetFloat( "lighterColorR" ); + lighter.shaderParms[ SHADERPARM_GREEN ] = spawnArgs.GetFloat( "lighterColorG" ); + lighter.shaderParms[ SHADERPARM_BLUE ] = spawnArgs.GetFloat( "lighterColorB" ); + lighter.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + lighter.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + lighter.lightRadius = spawnArgs.GetVector( "lighter_radius" ); + + lighter.origin = GetOrigin() + GetAxis() * spawnArgs.GetVector("offset_lighter"); + lighter.axis = mat3_identity; + + lighterHandle = gameRenderWorld->AddLightDef( &lighter ); + + StartSound("snd_lighter_on", SND_CHANNEL_ITEM); + } +} + +void hhPlayer::LighterOff() { + if (lighterHandle != -1) { + gameRenderWorld->FreeLightDef( lighterHandle ); + lighterHandle = -1; + StartSound("snd_lighter_off", SND_CHANNEL_ITEM); + } +} + +bool hhPlayer::IsLighterOn() const { //HUMANHEAD PCF mdl 05/04/06 - Made const + return (lighterHandle != -1); +} + +void hhPlayer::ToggleLighter() { + if (IsLighterOn()) { + LighterOff(); + } + else { + LighterOn(); + } +} + +void hhPlayer::UpdateLighter() { + if (IsLighterOn()) { + // Increase the lighter's temperature until it overheats + if (!gameLocal.isMultiplayer) { //rww - don't bother overheating the lighter in mp + lighterTemperature += spawnArgs.GetFloat( "lighterHeatRate", "0.025" ) * MS2SEC( USERCMD_MSEC ); + if (lighterTemperature >= 1.0f) { + // Too hot, turn the lighter off + StartSound("snd_lighter_toohot", SND_CHANNEL_ANY); + lighterTemperature = 1.0f; + + if (!(godmode || noclip)) { + LighterOff(); + } + } + } + } + else { + // Lighter is off, so decrease the lighter's temperature + if (lighterTemperature > 0) { + lighterTemperature -= spawnArgs.GetFloat( "lighterCoolRate", "0.05") * MS2SEC( USERCMD_MSEC ); + } + } +} + +/* +================== +hhPlayer::PlayFootstepSound +================== +*/ +void hhPlayer::PlayFootstepSound() { + if ( IsSpiritOrDeathwalking() ) { + return; // No footstep sounds in spiritwalk mode + } + + if ( IsWallWalking() ) { + // Special case wallwalk since contacts includes some non-wallwalk types + const char* soundKey = gameLocal.MatterTypeToMatterKey( "snd_footstep", SURFTYPE_WALLWALK ); + StartSound( soundKey, SND_CHANNEL_BODY3, 0, false, NULL ); + return; + } + + idPlayer::PlayFootstepSound(); +} + +/* +===================== +hhPlayer::PlayPainSound + +HUMANHEAD: aob +===================== +*/ +void hhPlayer::PlayPainSound() { + if( IsSpiritOrDeathwalking() ) { + return; + } + + //HUMANHEAD PCF rww 09/15/06 - female mp sounds + if (IsFemale()) { + StartSound( "snd_pain_small_female", SND_CHANNEL_VOICE ); + return; + } + //HUMANHEAD END + idPlayer::PlayPainSound(); +} + +/* +=============== +hhPlayer::Give +=============== +*/ +bool hhPlayer::Give( const char *statname, const char *value ) { + if( IsDead() ) { + return false; + } + + return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true ); +} + +/* +=============== +hhPlayer::ReportAttack +=============== +*/ +void hhPlayer::ReportAttack(idEntity *attacker) { + if (gameLocal.isServer && attacker) { //rww - broadcast event for this on the server + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(attacker), 32); + //unreliable since it's not super-important, and only send to this player. + ServerSendEvent(EVENT_REPORTATTACK, &msg, false, -1, entityNumber, true); + } + + int ix; + + for (ix=0; ix lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) { + lastAttackers[ix].attacker = NULL; + } + } + + // Add this attacker to first free slot, if any + for (ix=0; ix 0) { + lastAttackers[oldSlot].attacker = attacker; + lastAttackers[oldSlot].time = gameLocal.time; + lastAttackers[oldSlot].displayed = false; + } +} + +/* +================== +hhPlayer::Damage +================== +*/ +void hhPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + if( IsSpiritOrDeathwalking() ) { //Player is spirit-walking, so check for special immunities + const idKeyValue *kv = spawnArgs.MatchPrefix("immunityspirit"); + while( kv && kv->GetValue().Length() ) { + if ( !kv->GetValue().Icmp(damageDefName) ) { + return; + } + kv = spawnArgs.MatchPrefix("immunityspirit", kv); + } + + // If the player is spiritwalking, then any damage takes away spirit power instead of health + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + if ( IsSpiritWalking() ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef ) { + idPlayer *player = (attacker && attacker->IsType( idPlayer::Type )) ? static_cast(attacker) : NULL; + if ( !(gameLocal.gameType == GAME_TDM + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && !damageDef->GetBool( "noTeam" ) + && player + && player != this // you get self damage no matter what + && player->team == team) ) { + //rww - don't damage teammates' spirit when ff off + int damageWhenSpirit = damageDef->GetInt("damageWhenSpirit", "0"); //rww - special damage for knocking players back to body in mp when shot by other things (namely spirit arrows) + int oldSpirit = inventory.ammo[ ammo_spiritpower ]; + + int spiritDamage = (damageDef->GetInt( "damage", "1" ) * spawnArgs.GetFloat( "damageScaleInSpirit" ) * damageScale )+damageWhenSpirit; + if ( spiritDamage <= 0 && damageScale > 0 ) { + spiritDamage = 1; + } + + if ( !UseAmmo( ammo_spiritpower, spiritDamage ) ) { + inventory.ammo[ ammo_spiritpower ] = 0; // Clear spiritpower amount when returning from excessive damage + } + } + } + + lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code + + // Track last attacker for use in displaying HUD hit indicator + if (!gameLocal.isClient) { + ReportAttack(attacker); + } + + //HUMANHEAD rww - damage feedback for hitting spirit players in mp + if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) { + hhPlayer *killer = NULL; + if (attacker->IsType(idPlayer::Type)) { + killer = static_cast(attacker); + } + else if (attacker->IsType(hhVehicle::Type)) { + hhVehicle *veh = static_cast(attacker); + if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { + killer = static_cast(veh->GetPilot()); + } + } + + if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) { + if (killer == gameLocal.GetLocalPlayer()) { + assert(IsSpiritOrDeathwalking()); + if (gameLocal.gameType == GAME_TDM && team == killer->team) { + killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + else { + killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + + //hardcoded health ranges for the various flash colors + float h; + if (health > 100) { + h = 0.0f; + } + else if (health > 75) { + h = 0.25f; + } + else if (health > 25) { + h = 0.50f; + } + else { + h = 0.75f; + } + SetShaderParm(3, h); + SetShaderParm(5, -MS2SEC(gameLocal.time)*2); + } + } + else { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(this), 32); + killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber); + } + } + } + //HUMANHEAD END + + return; + } + } + + lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code + + // Track last attacker for use in displaying HUD hit indicator + if (!gameLocal.isClient) { + ReportAttack(attacker); + } + + // Check if the damage should "really kill" the player (when in deathwalk mode) + if ( IsDeathWalking() ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef ) { + if ( damageDef->GetBool( "reallyKill", "0" ) ) { // Truly kill the player + ReallyKilled( inflictor, attacker, damageDef->GetInt( "damage", "9999" ), dir, location ); + return; + } else if ( damageDef->GetBool( "spiritDamage", "0" ) ) { // Drain spirit power + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + UseAmmo( ammo_spiritpower, damageDef->GetInt( "damage", "1" ) * damageScale ); + return; + } + } + } + + //HUMANHEAD rww - keep track of last person to attack me, debounce time will vary based on ground contact + if (attacker && attacker->IsType(idPlayer::Type)) { + airAttacker = attacker; + if (!AI_ONGROUND) { + airAttackerTime = gameLocal.time + 300; + } + else { //if they are on the ground do a 100ms debounce in case they fall off a ledge or something as a result of leftover attack velocity + airAttackerTime = gameLocal.time + 100; + } + } + //HUMANHEAD END + + // CJR: DDA: Only in single player + if ( !gameLocal.isMultiplayer ) { + float newDamageScale = gameLocal.GetDDAValue() * 2.0f; // Scale damage from easy to hard + newDamageScale *= damageScale; + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef ) { + bool noGod = damageDef->GetBool("noGod", "0"); + if ( noGod ) { + // Make sure fatal damage bypasses post-deathwalk invulnerability + fl.takedamage = true; + } + } + + int oldHealth = health; + idPlayer::Damage( inflictor, attacker, dir, damageDefName, (const float)newDamageScale, location ); // CJR DDA TEST + + if ( attacker && attacker->IsType( hhMonsterAI::Type ) ) { // CJR DDA: Damaged by a monster, add the damage to the monster count + float delta = oldHealth - health; + + hhMonsterAI *monster = static_cast( attacker ); + + if ( monster ) { + monster->DamagedPlayer( delta ); + } + } + } else { + idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } + + //HUMANHEAD rww - hit feedback + if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) { //let's broadcast from the server only, so hit feedback is always reliable + hhPlayer *killer = NULL; + if (attacker->IsType(idPlayer::Type)) { + killer = static_cast(attacker); + } + else if (attacker->IsType(hhVehicle::Type)) { + hhVehicle *veh = static_cast(attacker); + if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { + killer = static_cast(veh->GetPilot()); + } + } + + if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) { + if (killer == gameLocal.GetLocalPlayer()) { + //don't provide visual indicator when shooting a teammate + if (gameLocal.gameType == GAME_TDM && team == killer->team) { + killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + else { + if (IsSpiritOrDeathwalking()) { + killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + else { + killer->StartSound( "snd_hitFeedback", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + //hardcoded health ranges for the various flash colors + float h; + if (health > 100) { + h = 0.0f; + } + else if (health > 75) { + h = 0.25f; + } + else if (health > 25) { + h = 0.50f; + } + else { + h = 0.75f; + } + SetShaderParm(3, h); + SetShaderParm(5, -MS2SEC(gameLocal.time)*2); + } + } + else { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(this), 32); + killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber); + } + + //see how this feels (and more importantly how destructive it is toward bandwidth) + //killer->mpHitFeedbackTime = gameLocal.time + USERCMD_MSEC; + } + } + //HUMANHEAD END + + if ( bFrozen ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( damageDef && damageDef->GetInt( "free_cocoon", "0" ) ) { + Event_Unfreeze(); + } + } +} + +void hhPlayer::DoDeathDrop() { + // General dropping (for monsters, souls, etc.) + const idKeyValue *kv = NULL; + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while ( kv ) { + + idStr drops = kv->GetValue(); + idDict args; + + idStr last5 = kv->GetKey().Right(5); + if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) { + + args.Set( "classname", drops ); + + // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up + args.Set("monsterSpawnedBy", name.c_str()); + + idVec3 origin; + idMat3 axis; + idStr jointKey = kv->GetKey() + idStr("Joint"); + idStr jointName = spawnArgs.GetString( jointKey ); + idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint"); + idStr j2jName = spawnArgs.GetString( joint2JointKey ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( args, &newEnt ); + HH_ASSERT(newEnt != NULL); + + if(jointName.Length()) { + jointHandle_t joint = GetAnimator()->GetJointHandle( jointName ); + if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name ); + origin = renderEntity.origin; + axis = renderEntity.axis; + } + axis *= renderEntity.axis; + origin = renderEntity.origin + origin * renderEntity.axis; + newEnt->SetAxis(axis); + newEnt->SetOrigin(origin); + } + else { + newEnt->SetAxis(viewAxis); + newEnt->SetOrigin(GetOrigin()); + } + } + + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +//HUMANHEAD rww +/* +================== +hhPlayer::Event_RespawnCleanup +performs any necessary operations on death to prepare for a clean spawn. +only cosmetic things should be placed here, this function will not be called +for sure on death, at least for the client. +================== +*/ +void hhPlayer::Event_RespawnCleanup(void) { + //remove any fx entities which are bound to the player + idEntity *ent; + idEntity *next; + + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->GetTeamChain(); + if ( ent->GetBindMaster() == this && ent->IsType(hhEntityFx::Type) ) { + ent->Unbind(); + if( !ent->fl.noRemoveWhenUnbound ) { + ent->PostEventMS( &EV_Remove, 0 ); + if (gameLocal.isClient) { + ent->Hide(); + } + } + next = teamChain; + } + } +} +//HUMANHEAD END + +/* +================== +hhPlayer::Killed +================== +*/ +void hhPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //you only die once + if( AI_DEAD ) { + return; + } + + CancelEvents(&EV_DamagePlayer); //rww - don't do any more posted damage events once dead + + if (gameLocal.isMultiplayer) { //rww - this was not handled at all, i guess because there are different circumstances for "death" in sp + wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false ); + spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + LighterOff(); + + if (!gameLocal.isClient && weapon.IsValid() && weapon->IsType(hhWeapon::Type) && weapon->CanDrop()) { //when dying in mp, toss my weapon out. + idVec3 forward, up; + + viewAngles.ToVectors( &forward, NULL, &up ); + //rww - hackishness to keep the type of ammo in the leechgun that it was dropped with + idEntity *dropped = weapon->DropItem( 50.0f * forward + 50.0f * up, 0, 60000, true ); + if (dropped && weapon->IsType(hhWeaponSoulStripper::Type)) { + dropped->spawnArgs.Set("def_droppedEnergyType", inventory.energyType); + } + } + } + + if (!gameLocal.isClient) { + DoDeathDrop(); + } + + if ( gameLocal.isMultiplayer && !gameLocal.IsCooperative() ) { + bDeathWalk = false; + bSpiritWalk = false; + bReallyDead = true; + } + else if ( !inventory.requirements.bCanDeathWalk || !gameLocal.DeathwalkMapLoaded() ) { + bDeathWalk = false; + bSpiritWalk = false; + bReallyDead = true; + } + + //First thing we do is get out of vehicle + if (InVehicle()) { + GetVehicleInterface()->GetVehicle()->EjectPilot(); + } + + // HUMANHEAD cjr: Update DDA + if ( !AI_DEAD && !gameLocal.isMultiplayer ) { + gameLocal.GetDDA()->DDA_AddDeath( this, attacker ); + } + // HUMANHEAD END + + if ( attacker && attacker->spawnArgs.GetBool("death_look_at", "0")) { + attacker->spawnArgs.GetString("death_look_at_bone", "", deathLookAtBone); + attacker->spawnArgs.GetString("death_camera_bone", "", deathCameraBone ); + deathLookAtEntity = attacker; + } + + if ( weapon.IsValid() ) { + //HUMANHEAD bjk 04/26/06 - no sniper scope when dead + if ( weapon->IsType( hhWeaponRifle::Type ) ) { + static_cast( weapon.GetEntity() )->ZoomOut(); + } + weapon->PutAway(); + } + + // HUMANHEAD nla + if ( hand.IsValid() ) { + hand->PutAway(); + } + // HUMANHEAD END + + if ( !bReallyDead ) { // Only go into deathwalk mode if the player is in the transitional death state + idVec3 origin; + idMat3 axis; + idVec3 viewDir; + idAngles angles; + idMat3 eyeAxis; + GetResurrectionPoint( origin, axis, viewDir, angles, eyeAxis, GetPhysics()->GetAbsBounds(), GetOrigin(), GetPhysics()->GetAxis(), GetAxis()[0], viewAngles ); + DeathWalk( origin, axis, viewDir.ToMat3(), angles, eyeAxis ); + } else { + if (gameLocal.isMultiplayer) { //HUMANHEAD rww + PostEventMS(&EV_RespawnCleanup, 32); + if (!gameLocal.isClient) { + StartRagdoll(); //start to rag on the player so that the proxy copies off proper af status + + GetPhysics()->SetContents( 0 ); //make non-solid + + hhMPDeathProxy *prox = (hhMPDeathProxy *)hhSpiritProxy::CreateProxy( spawnArgs.GetString("def_deathProxy_mp"), this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); + assert(((hhSpiritProxy *)prox)->IsType(hhMPDeathProxy::Type)); + if (prox) { + idVec3 flingForce; + float capDmg = (float)damage; + if (capDmg > 200.0f) { + capDmg = 200.0f; + } + + flingForce = dir; + flingForce.Normalize(); + flingForce *= capDmg; + + prox->GetPhysics()->AddForce(0, prox->GetPhysics()->GetOrigin(0), flingForce*256.0f*256.0f); + prox->SetFling(prox->GetPhysics()->GetOrigin(0), flingForce); + } + + StopRagdoll(); //stop again, as we don't need to be ragging on the actual player + } + + Hide(); //hide player + + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + 10000; + } else { + GetPhysics()->SetContents(0); + Hide(); + + minRespawnTime = gameLocal.time + 2000; + maxRespawnTime = minRespawnTime + 5000; + } //HUMANHEAD END + + physicsObj.SetMovementType( PM_DEAD ); + SAFE_REMOVE( weapon ); + } + + AI_DEAD = true; + + //HUMANHEAD rww + if (gameLocal.isMultiplayer) { + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); + SetWaitState( "" ); + } + //HUMANHEAD END + + //HUMANHEAD PCF rww 09/15/06 - female mp sounds + if (IsFemale()) { + StartSound( "snd_death_female", SND_CHANNEL_VOICE ); + } + else { + //HUMANHEAD END + StartSound( "snd_death", SND_CHANNEL_VOICE ); + } + + if ( gameLocal.isMultiplayer && !gameLocal.isCoop ) { + idPlayer *killer = NULL; + + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + } + else { //rww - otherwise try to credit airAttacker + if (airAttacker.IsValid() && + airAttacker.GetEntity() && + airAttackerTime > gameLocal.time) { + + if (airAttacker->IsType(idPlayer::Type)) { + killer = static_cast(airAttacker.GetEntity()); + } + else if (airAttacker->IsType(hhVehicle::Type)) { + hhVehicle *veh = static_cast(airAttacker.GetEntity()); + if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) { + killer = static_cast(veh->GetPilot()); + } + } + } + } + gameLocal.mpGame.PlayerDeath( this, killer, inflictor, false ); //HUMANHEAD rww - pass inflictor + } + + UpdateVisuals(); + + airAttackerTime = 0; //HUMANHEAD rww - reset air attacker time once dead +} + +/* +============== +hhPlayer::Kill +============== +*/ +void hhPlayer::Kill( bool delayRespawn, bool nodamage ) { + if (noclip) { // HUMANHEAD pdm: Because of deathwalk, this shouldn't be allowed + return; + } + + if ( health > 0 ) { + //HUMANHEAD rww + if (IsSpiritOrDeathwalking()) { + if (!IsSpiritWalking()) { //don't "kill" when dead. + return; + } + StopSpiritWalk(true); + } + //HUMANHEAD END + + godmode = false; + health = 0; + //HUMANHEAD rww - if in a vehicle, eject now + if (InVehicle()) { + if (vehicleInterfaceLocal.GetVehicle()) { + vehicleInterfaceLocal.GetVehicle()->EjectPilot(); + } + } + //HUMANHEAD END + Damage( NULL, NULL, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + } +} + +/* +=============== +hhPlayer::DetermineOwnerPosition + +HUMANHEAD: aob +=============== +*/ +void hhPlayer::DetermineOwnerPosition( idVec3 &ownerOrigin, idMat3 &ownerAxis ) { + ownerAxis = TransformToPlayerSpace( GetUntransformedViewAxis() ); + ownerOrigin = cameraInterpolator.GetCurrentPosition() + idVec3(g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat()) * GetAxis(); +} + +/* +=============== +hhPlayer::GetViewPos +=============== +*/ +//HUMANHEAD bjk +void hhPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) { + idAngles angles; + + // if dead, fix the angle and don't add any kick + // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + if ( IsDead() && !gameLocal.isMultiplayer ) { //rww - don't want this in mp. + // HUMANHEAD END + angles.yaw = viewAngles.yaw; + angles.roll = 40; + angles.pitch = -15; + axis = angles.ToMat3(); + origin = GetEyePosition(); + } else { + assert(kickSpring < 500.0f && kickSpring > 0.0f); + assert(kickDamping < 500.0f && kickDamping > 0.0f); + origin = viewBob + TransformToPlayerSpace( idVec3(g_viewNodalX.GetFloat(), g_viewNodalZ.GetFloat(), g_viewNodalZ.GetFloat() + EyeHeight()) ); + axis = TransformToPlayerSpace( (GetUntransformedViewAngles() + viewBobAngles + playerView.AngleOffset(kickSpring, kickDamping)).ToMat3() ); + } +} + +/* +=============== +hhPlayer::CalculateRenderView +=============== +*/ +void hhPlayer::CalculateRenderView( void ) { + idPlayer::CalculateRenderView(); + + // HUMANHEAD cjr + if ( IsSpiritOrDeathwalking() ) { // If spiritwalking, then allow the player to see special objects + renderView->viewSpiritEntities = true; + } +} + + +/* +=============== +hhPlayer::OffsetThirdPersonView + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +=============== +*/ +void hhPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { + idVec3 view; + trace_t trace; + idMat3 lookAxis; + idVec3 origin; + idAngles angles( GetUntransformedViewAngles() ); + + if ( angle ) { + angles.pitch = 0.0f; + } + + if ( angles.pitch > 45.0f && !InVehicle() ) { + angles.pitch = 45.0f; // don't go too far overhead + } + + //HUMANHEAD: aob + lookAxis = TransformToPlayerSpace( angles.ToMat3() * idAngles(0.0f, angle, 0.0f).ToMat3() ); + if( InVehicle() ) { + origin = GetOrigin() + lookAxis[2] * (height + EyeHeight()); + } else { + origin = GetEyePosition() + GetEyeAxis()[2] * height; + } + view = origin + lookAxis[0] * -range; + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 16 by 16 block to prevent the view from near clipping anything + if( !noclip ) { + int mask = MASK_SHOT_BOUNDINGBOX; + + if (gameLocal.isMultiplayer) { //rww - don't want this hitting corpses. also, shouldn't we pay attention to "clip"? + mask = MASK_PLAYERSOLID; + } + + thirdPersonCameraClipBounds.SetOwner( this ); + idEntity* ignore = (InVehicle()) ? GetVehicleInterface()->GetVehicle() : (idEntity*)this; + gameLocal.clip.Translation( trace, origin, view, &thirdPersonCameraClipBounds, lookAxis, mask, ignore ); + range *= trace.fraction; + } + + renderView->vieworg = origin + lookAxis[0] * -range; + renderView->viewaxis = lookAxis; + //HUMANHEAD END + renderView->viewID = 0; +} + + +/* +============================== +hhPlayer::GetGuiHandInfo + Retrns the proper gui hand info for a given gui +============================== +*/ +const char *hhPlayer::GetGuiHandInfo() { + const char *attrib; + idDict *item = NULL; + const char *handString; + + // Set the gui to: + // "required_attribute" "Hunter Hand" + // If item exists in player inventory, player uses guihand specified by item + if (!IsSpiritWalking() && focusGUIent && focusGUIent->spawnArgs.GetString("required_attribute", NULL, &attrib)) { + item = FindInventoryItem( attrib ); + if ( item ) { + if (item->GetString("def_guihand", NULL, &handString)) { + return handString; + } + } + } + + // If no required attribute/player doesn't have the item, check for a def_guihand string + if ( focusGUIent && focusGUIent->spawnArgs.GetString( "def_guihand", NULL, &handString ) ) { + return( handString ); + } + + // Otherwise, use the default player hand + return spawnArgs.GetString("def_guihand"); + +} + + +//============================================================================= +// +// Spirit walk functions +// +//============================================================================= + +void hhPlayer::StartSpiritWalk( const bool bThrust, bool force ) { + hhFxInfo fxInfo; + idVec3 origin; + idMat3 axis; + + //rww - don't do anything on the client + if (gameLocal.isClient) { + return; + } + + if ( bReallyDead ) { // Don't allow spiritwalking if truly dead + if (force) { + gameLocal.Error("Attempted to force spirit walk when dead.\n"); + } + return; + } + + // Make sure spirit walk isn't disabled + if ( !bAllowSpirit ) { + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); + return; + } + + spiritWalkToggleTime = gameLocal.time; + + if ( gameLocal.isMultiplayer ) { // CJR: Don't allow spiritwalking in MP unless the player has spirit power + if ( GetSpiritPower() <= 0 ) { + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY, 0, true); + return; + } + } + + if ( !force && nextSpiritTime > gameLocal.time ) { // mdl: Make sure they didn't just get knocked back into their body by a wraith + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); + return; + } + + if (!IsSpiritOrDeathwalking()) { + if ( !gameLocal.IsLOTA() ) { // In LOTA, spirit power never drains + PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS ); + } + + if ( !bThrust ) { // Normal spiritwalking + EnableEthereal( spawnArgs.GetString( "def_spiritProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); + } else { // Knocked out by a wraith + EnableEthereal( spawnArgs.GetString( "def_possessedProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() ); + } + + SelectEtherealWeapon(); + + spiritwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + // Update HUD + if (hud) { + hud->HandleNamedEvent("SwitchToEthereal"); + } + + // Set the player's skin to a glowy effect + SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") ); + SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made + + // Spawn in a flash + GetViewPos( origin, axis ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetBindBone( "origin" ); + BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo ); + + // Thrust the player backwards out of the body + if ( bThrust ) { + idVec3 vec = GetPhysics()->GetAxis()[0] * -200.0f + GetPhysics()->GetAxis()[2] * 50.0f; + + physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + vec ); + // set the timer so that the player can't cancel out the movement immediately + physicsObj.SetKnockBack( 100 ); + } + + // bg - trigger map entity, provides a simple hook for map/scripts + idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkEntered" ); + if( swTrig ) { + swTrig->PostEventMS( &EV_Activate, 0, this ); + } + } +} + +void hhPlayer::StopSpiritWalk(bool forceAllowance) { + hhFxInfo fxInfo; + idVec3 origin; + idMat3 axis; + + //rww - don't do anything on the client + if (gameLocal.isClient) { + return; + } + + if ( IsPossessed() ) { // If possessed and the player stops spiritwalking, they die + PossessKilled(); + return; + } + + spiritWalkToggleTime = gameLocal.time; + + if ( bReallyDead ) { // Don't allow spiritwalking if truly dead + return; + } + + CancelEvents(&EV_SetOverlayMaterial); //HUMANHEAD rww - if being forced back out of spirit mode quickly enough, don't set overlay afterward + + if (IsSpiritOrDeathwalking()) { + CancelEvents(&EV_DrainSpiritPower); + if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) { + hhWeaponSpiritBow *bow = static_cast( weapon.GetEntity() ); + if( bow->BowVisionIsEnabled() ) { + bow->StopBowVision(); + } + } + DisableEthereal(); + spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + SetSkin( NULL ); + + // bg - trigger map entity, provides a simple hook for map/scripts + idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkExited" ); + if( swTrig ) { + swTrig->PostEventMS( &EV_Activate, 0, this ); + } + + // Update HUD + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + buttonMask |= BUTTON_ATTACK; //HUMANHEAD bjk + + // Spawn in a flash + GetViewPos( origin, axis ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetBindBone( "origin" ); + BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo ); + } +} + +void hhPlayer::ToggleSpiritWalk( void ) { + if (spectating) { //rww - do not allow spectators to spirit walk. + return; + } + + if ( bPossessed ) { // Cannot toggle away from spiritwalk when possessed + StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY); + return; + } + + // spiritwalk time check -- force a delay between spiritwalking and not + if (gameLocal.time < spiritWalkToggleTime + 250) { + return; + } + + if( IsSpiritOrDeathwalking() ) { + StopSpiritWalk(); + } else { + StartSpiritWalk( false ); + } +} + +//HUMANHEAD rww +void hhPlayer::RestorePlayerLocationFromDeathwalk( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ) { + idVec3 newOrigin; + idMat3 newAxis; + idVec3 newViewDir; + idAngles newAngles; + + if( deathwalkLastCrouching ) { + ForceCrouching(); + } + + SetEyeAxis(deathwalkLastEyeAxis); + GetResurrectionPoint( newOrigin, newAxis, newViewDir, newAngles, deathwalkLastEyeAxis, GetPhysics()->GetAbsBounds(), deathwalkLastOrigin, deathwalkLastBBoxAxis, viewDir, angles ); + Teleport( newOrigin, newAxis, newViewDir, (newAngles.ToMat3() * deathwalkLastEyeAxis.Transpose()).ToAngles(), NULL ); +} +//HUMANHEAD END + +//============================================================================= +// +// hhPlayer::EnableEthereal +// +// Sets the player into an ethereal state (used for both spiritwalk and deathwalk) +// This function does the following: +// - Fade volume down on all class 0 sounds +// - Sets bSpiritWalk (since this is true for both spirit and death walks) +// - Spawns a proxy +// - Sets the new collision +// - Disables the weapons (the bow is enabled seperately for spiritwalk) +// - Disables the lighter +// - Unpossesses the player (if possessed) +// - Updates bindings +//============================================================================= + +void hhPlayer::EnableEthereal( const char *proxyName, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + //rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE + if (gameLocal.isClient) { + return; + } + + // Turn on low-pass effect + if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers + gameLocal.SpiritWalkSoundMode( true ); + } + + // Spawn proxy Tommy actor + spiritProxy = hhSpiritProxy::CreateProxy( proxyName, this, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis ); + + // Change player's collision type (to allow walking through forcefields, etc) + GetPhysics()->SetClipMask( MASK_SPIRITPLAYER ); + fl.acceptsWounds = false; + spawnArgs.Set("produces_splats", "0"); + + // HUMANHEAD mdl: Let our enemies know about the spirit proxy + for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) { + if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) { + hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsSpirit, this, spiritProxy.GetEntity() ); + } + } + + // If bound to another object (like on a rail ride), swap bindings with the proxy + if ( GetBindMaster() != NULL ) { + if (GetBindMaster()->IsType(hhBindController::Type)) { + hhBindController *binder = static_cast(GetBindMaster()); + bool loose = binder->IsLoose(); + binder->Detach(); + binder->Attach( spiritProxy.GetEntity(), loose ); + } else { + gameLocal.Warning("Handle this: spiritwalking while bound to non-rail"); + SwapBindInfo( spiritProxy.GetEntity() ); // untested + } + } + + SnapDownCurrentWeapon(); + + LighterOff(); + + // Set the player into spiritwalk mode + bSpiritWalk = true; +} + +//============================================================================= +// +// hhPlayer::DisableEthereal +// +// Returns the player from an ethereal state (used for both spiritwalk and deathwalk) +// This function does the following: +// - Clears bSpiritWalk (since this is true for both spirit and death walks) +// - Removes the proxy +// - Sets the new collision +// - Re-enables the weapons +// - Updates bindings +// - Fade volume back up on all class 0 sounds +//============================================================================= + +void hhPlayer::DisableEthereal( void ) { + hhFxInfo fxInfo; + idVec3 boneOffset; + idMat3 boneAxis; + + //rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE + if (gameLocal.isClient) { + return; + } + + bSpiritWalk = false; + + // Spawn in an effect when the spirit is snapped back + GetJointWorldTransform( "waist", boneOffset, boneAxis ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetNormal( boneAxis[1] ); + BroadcastFxInfo( spawnArgs.GetString( "fx_spiritReturn" ), boneOffset, GetAxis(), &fxInfo ); + + if( spiritProxy.IsValid() ) { + // Handle case of returning to a body on a rail ride + idEntity *spiritMaster = spiritProxy->GetBindMaster(); + if (spiritMaster != NULL) { + if (spiritMaster->IsType(hhBindController::Type)) { + hhBindController *binder = static_cast(spiritMaster); + bool loose = binder->IsLoose(); + binder->Detach(); + spiritProxy->DeactivateProxy(); + binder->Attach(this, loose); + } else if (spiritMaster->IsType(hhMonsterAI::Type)) { + if (reinterpret_cast (spiritMaster)->GetEnemy() != static_cast (spiritProxy.GetEntity())) { + gameLocal.Warning("Monster has spirit proxy bound to it but spirit proxy is not it's enemy!\n"); + } + spiritProxy->DeactivateProxy(); + } else { + gameLocal.Warning("Handle this: spiritwalking while bound to non-rail"); + idEntity *temp = spiritProxy.GetEntity(); // this removed by DeactivateProxy + spiritProxy->DeactivateProxy(); + SwapBindInfo(temp); // untested + } + } + else { // Normal case + spiritProxy->DeactivateProxy(); + } + } + + PutawayEtherealWeapon(); + + GetPhysics()->SetClipMask( MASK_PLAYERSOLID ); + fl.acceptsWounds = true; + spawnArgs.Set("produces_splats", "1"); + + // Turn off low-pass effect + if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers + gameLocal.SpiritWalkSoundMode( false ); + } + + // HUMANHEAD mdl: Let our enemies know the spirit proxy is going away + for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) { + if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == spiritProxy.GetEntity() ) { + hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, spiritProxy.GetEntity() ); + } else if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) { // Targetting spirit that is going away + hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, NULL ); + } + } + + //HUMANHEAD rww - clear the focus now that we've changed positions by switching out of spirit mode + focusTime = 0; + UpdateFocus(); + //HUMANHEAD END + + spiritProxy = NULL; +} + +//============================================================================= +// +// hhPlayer::GetResurrectionPoint +// +// Get the position/orientation for resurrection after deathwalk +//============================================================================= +void hhPlayer::GetResurrectionPoint( idVec3& origin, idMat3& axis, idVec3& viewDir, idAngles& angles, idMat3& eyeAxis, const idBounds& absBounds, const idVec3& defaultOrigin, const idMat3& defaultAxis, const idVec3& defaultViewDir, const idAngles& defaultAngles ) { + idClipModel *clipModelList[ MAX_GENTITIES ]; + idClipModel* clipModel = NULL; + idEntity* entity = NULL; + hhSafeResurrectionVolume* volume = NULL; + + int num = gameLocal.clip.ClipModelsTouchingBounds( absBounds, CONTENTS_DEATHVOLUME, clipModelList, MAX_GENTITIES ); + for( int ix = 0; ix < num; ++ix ) { + clipModel = clipModelList[ix]; + + if( !clipModel ) { + continue; + } + + entity = clipModel->GetEntity(); + if( !entity || !entity->IsType(hhSafeResurrectionVolume::Type) ) { + continue; + } + + volume = static_cast( entity ); + volume->PickRandomPoint( origin, axis ); + viewDir = axis[0]; + angles = axis.ToAngles(); + eyeAxis = mat3_identity; + return; + } + + eyeAxis = GetEyeAxis(); + origin = defaultOrigin; + axis = defaultAxis; + viewDir = defaultViewDir; + angles = defaultAngles; + +// gameLocal.Printf("Deathwalk @:\n"); +// gameLocal.Printf(" origin: %s\n", origin.ToString(0)); +// gameLocal.Printf(" axis: %s\n", axis.ToString(2)); +// gameLocal.Printf(" viewDir: %s\n", viewDir.ToString(2)); +// gameLocal.Printf(" angles: %s\n", angles.ToString(0)); +} + + +//============================================================================= +// +// hhPlayer::SquishedByDoor +// +//============================================================================= +void hhPlayer::SquishedByDoor(idEntity *door) { + if ( door == this ) { // Don't allow the squished code to send a player back it the squisher is the player itself + return; + } + + // If there is a spirit player in the door, make him go back to physical so he doesn't become stuck + if (IsSpiritWalking()) { + StopSpiritWalk(); + } +} + +//============================================================================= +// +// hhPlayer::UpdatePossession +// +// Updates the possession effects (if the player is possessed) +// +// HUMANHEAD cjr +//============================================================================= + +void hhPlayer::UpdatePossession( void ) { + + if ( !IsPossessed() ) { // Player is not possessed + return; + } + +/* todo: + possessionTimer -= MS2SEC( USERCMD_MSEC ); + + // Update a shaderparm for the view screen to update based upon possessionTimer + possessionMax = spawnArgs.GetFloat( "possessionTime", "8" ); + possessionScale = possessionTimer / possessionMax; + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, possessionScale ) ); + + // Mess with the player's FOV while they are being possessed + possessionFOV = g_fov.GetFloat() + (1.0f - possessionScale) * 60 + 1.5f * sin( MS2SEC(gameLocal.time) * 3 ); + + if ( possessionTimer <= 0 ) { // The player is now fully possessed, kill them + // TODO: Extra code to show the possessed player running around and such? + Unpossess(); + Damage( NULL, NULL, vec3_origin, "damage_crush", 1.0f, INVALID_JOINT ); + } +*/ +} + +//============================================================================= +// +// hhPlayer::RechargeHealth +// +// Recharges the player's health to 25% +// +// This is done to avoid gameplay issues where the player has only a few points +// of health. The game will still be tense if the player is at 25%, but +// will play more fairly. +// +// HUMANHEAD cjr +//============================================================================= + +void hhPlayer::Event_RechargeHealth( void ) { + + if ( health > 0 && health < spawnArgs.GetInt( "healthRecharge", "25" ) && !IsSpiritOrDeathwalking() ) { // Only recharge if the player is alive and in the critical zone + if ( gameLocal.time - lastDamagedTime >= spawnArgs.GetInt( "healthRechargeDelay", "1500" ) ) { // Delay before recharging + health++; + } + } + + PostEventSec( &EV_RechargeHealth, spawnArgs.GetFloat( "healthRechargeRate", "0.5" ) ); +} + +//============================================================================= +// +// hhPlayer::RechargeRifleAmmo +// +// Recharges rifle ammo if the player is very low. This is done to guarantee +// that the player has at least some ammo to solve puzzles that require shooting +// something such as a gravity switch. +// +// HUMANHEAD cjr +//============================================================================= + +void hhPlayer::Event_RechargeRifleAmmo( void ) { + + int ammoIndex = inventory.AmmoIndexForAmmoClass( "ammo_rifle" ); + int ammoCount = inventory.ammo[ ammoIndex ]; + + int maxAmmo = spawnArgs.GetInt( "rifleAmmoRechargeMax", "20" ); + if ( ammoCount < maxAmmo && !AI_ATTACK_HELD ) { // CJR PCF 04/26/06 + inventory.ammo[ ammoIndex ] += 2; + if ( inventory.ammo[ ammoIndex ] > maxAmmo ) { + inventory.ammo[ ammoIndex ] = maxAmmo; + } + } + + PostEventSec( &EV_RechargeRifleAmmo, spawnArgs.GetFloat( "rifleAmmoRechargeRate", "2" ) ); +} + +//============================================================================= +// +// hhPlayer::Event_DrainSpiritPower +// +// HUMANHEAD pdm: 10 Hz timer used to drain spirit power at differing rates +//============================================================================= +void hhPlayer::Event_DrainSpiritPower() { + + // JRM: God mode, don't drain! + if(godmode) { + return; + } + + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + + if ( gameLocal.isMultiplayer ) { // CJR: Drain spirit power in MP + if ( !UseAmmo( ammo_spiritpower, 1 ) ) { + StopSpiritWalk(); + return; + } + } + + // spirit bow alt mode drain + if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) { + hhWeaponSpiritBow *bow = static_cast( weapon.GetEntity() ); + if( bow->BowVisionIsEnabled() ) { + if( !UseAmmo(ammo_spiritpower, 1) ) { // Bow vision drains spirit power + return; + } + } + } + + PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS ); +} + +//============================================================================= +// +// hhPlayer::DeathWalk +// +//============================================================================= + +void hhPlayer::DeathWalk( const idVec3& resurrectOrigin, const idMat3& resurrectBBoxAxis, const idMat3& resurrectViewAxis, const idAngles& resurrectViewAngles, const idMat3& resurrectEyeAxis ) { + if ( IsSpiritWalking() ) { // Ensure that two proxies cannot be active at the same time + StopSpiritWalk(); + } + + bDeathWalk = true; + deathWalkTime = gameLocal.time; // Get the time of death + + health = 0; // Force health to zero + + deathWalkPower = spawnArgs.GetInt( "deathWalkPowerStart" ); // Power in deathwalk. When full, the player will return + fl.takedamage = false; // can no longer be damaged in deathwalk mode + + if (hud) { + hud->HandleNamedEvent("SwitchToEthereal"); + } + + // Disable clip until we get to deathwalk + bInDeathwalkTransition = true; + + //rww - for deathwalk, let's store these values on the player instead of relying on the death proxy + deathwalkLastOrigin = resurrectOrigin; + deathwalkLastBBoxAxis = resurrectBBoxAxis; + deathwalkLastViewAxis = resurrectViewAxis; + deathwalkLastViewAngles = resurrectViewAngles; + deathwalkLastEyeAxis = resurrectEyeAxis; + deathwalkLastCrouching = IsCrouching(); + + EnableEthereal( spawnArgs.GetString("def_deathProxy"), resurrectOrigin, resurrectBBoxAxis, resurrectViewAxis, resurrectViewAngles, resurrectEyeAxis ); + // Set the overlay material for DeathWalk + playerView.SetViewOverlayColor( colorWhite ); // Guarantee that the color is reset + PostEventMS( &EV_SetOverlayMaterial, 1, spawnArgs.GetString("mtr_deathWalk"), false ); + + //Don't allow weapon use for a bit + if ( weapon.GetEntity() ) { + weapon.GetEntity()->PutAway(); + } + + // Alert the scripts that death has occured so they can manage music + idEntity *dwJustDied = gameLocal.FindEntity("dw_justdied"); + if (dwJustDied) { + dwJustDied->PostEventMS(&EV_Activate, 0, this); + } + + deathwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + // Flash the screen before entering death world + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); + deathWalkFlash = 0.0f; + possessionFOV = 90.0f; + + CancelEvents( &EV_RechargeHealth ); + CancelEvents( &EV_RechargeRifleAmmo ); + + // If this is our first time, tell the script + if (!gameLocal.IsCompetitive() && !inventory.bHasDeathwalked) { + const function_t *firstFunc = gameLocal.program.FindFunction( "map_deathwalk::FirstDeathwalk" ); + if ( firstFunc ) { + idThread *thread = new idThread(); + thread->CallFunction( firstFunc, false ); + thread->DelayedStart( 0 ); + } + } + + //rww - if there is a deathwalk portal, let's create a target for it and activate it + if (!gameLocal.FindEntity("dw_deathLocation")) { //check to see if we've made ourselves a target yet + idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" ); + if (dwPortal) { + trace_t tr; + idVec3 camPos, camDir; + idAngles camAngles; + idDict args; + + //FIXME more complex logic that allows angles to change based on collision circumstances + //do wider trace that takes the actual view frustum of camera into account + //check for collisions with the actual view "plane" + camPos = resurrectOrigin; + camPos += resurrectBBoxAxis[2] * 64.0f; + gameLocal.clip.TracePoint(tr, resurrectOrigin, camPos, CONTENTS_SOLID, this); + camDir = resurrectOrigin-camPos; + camAngles = camDir.ToAngles(); + + args.Clear(); + args.SetVector("origin", tr.endpos); + args.SetAngles("angles", camAngles); + args.SetMatrix("rotation", camAngles.ToMat3()); + args.Set("name", "dw_deathLocation"); + gameLocal.SpawnObject("info_null", &args); + + dwPortal->ProcessEvent(&EV_UpdateCameraTarget); //update for the newly placed cameraTarget + dwPortal->PostEventMS(&EV_Activate, 0, this); + if (dwPortal->cameraTarget) { //if camera target was legitimate, force an update to the remoteRenderView now + dwPortal->GetRenderEntity()->remoteRenderView = dwPortal->cameraTarget->GetRenderView(); + } + } + } + + Hide(); // Don't show the player when they are in the limbo state between alive and dead + PostEventSec( &EV_PrepareForDeathWorld, spawnArgs.GetFloat( "prepareForDeathWorldDelay", "1.5" ) ); +} + +//============================================================================= +// +// hhPlayer::Event_PrepareForDeathWorld +// +// Flashes the screen / fades the FOV +//============================================================================= + +void hhPlayer::Event_PrepareForDeathWorld() { + deathWalkFlash += spawnArgs.GetFloat( "deathWalkFlashChange", "0.05" ); + if ( deathWalkFlash >= 1.0f ) { + deathWalkFlash = 1.0f; + PostEventSec( &EV_EnterDeathWorld, 0.0f ); // Actually send player into the deathworld + } else { + PostEventSec( &EV_PrepareForDeathWorld, 0.02f ); + } + + possessionFOV += spawnArgs.GetFloat( "deathWalkFOVChange", "3.0" ); + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) ); +} + +//============================================================================= +// +// hhPlayer::Event_EnterDeathWorld +// +// - Teleports the player to the death world +// - Spawns the death wraiths +// - Gives the player a weapon +//============================================================================= + +void hhPlayer::Event_EnterDeathWorld() { + // Reset player view information + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); // Turn off the flash + possessionFOV = 0.0f; + bDeathWalkStage2 = false; + + // Spawn in DeathWraiths after a short period of time + if ( !inventory.bHasDeathwalked ) { // Delay the wraiths the first time the player deathwalks + int firstDelay = spawnArgs.GetInt( "deathWalk_firstTimeDelay", "15000" ); + PostEventMS( &EV_SpawnDeathWraith, firstDelay ); // CJR: delay the deathwraiths for several seconds while the Grandfather DW speech is going on + PostEventMS( &EV_AdjustSpiritPowerDeathWalk, firstDelay + 2000 ); + inventory.bHasDeathwalked = true; + } else { + PostEventMS( &EV_SpawnDeathWraith, 0 ); // CJR: Non-first time, spawn the wraiths instantly + PostEventMS( &EV_AdjustSpiritPowerDeathWalk, 2000 ); + } + + // Give the player the spirit bow + SelectEtherealWeapon(); + + // get the entity which tells us where we want to place our dw proxy + idEntity *dwProxyPosEnt = gameLocal.FindEntity( "dw_floatingBodyMarker" ); + if (dwProxyPosEnt) { + //if we found that, then this is the new deathwalk, so create the deathwalk proxy. + hhDeathWalkProxy *dwProxy = (hhDeathWalkProxy *)gameLocal.SpawnObject( spawnArgs.GetString("def_deathWalkProxy"), NULL ); + if (!dwProxy) { + gameLocal.Error("hhPlayer::Event_EnterDeathWorld: Could not create hhDeathWalkProxy"); + return; + } + idAngles dwProxyAngles; + dwProxyPosEnt->spawnArgs.GetAngles("angles", "0 0 0", dwProxyAngles); + dwProxy->ActivateProxy(this, dwProxyPosEnt->GetOrigin(), dwProxyAngles.ToMat3(), dwProxyAngles.ToMat3(), dwProxyAngles, GetEyeAxis()); + } + + idEntity* deathWalkPlayerStartManager = gameLocal.FindEntity( "dw_deathWalkPlayerStartManager" ); + if( !deathWalkPlayerStartManager ) { + return; + } + + idEntity* deathWalkStart = deathWalkPlayerStartManager->PickRandomTarget(); + if( deathWalkStart ) { + deathWalkStart->ProcessEvent( &EV_Activate, this ); + } + + // Trigger the 'DeathWalkEntered' entity + // This allows the scripters to do something upon entering deathwalk + idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkEntered"); + if (dwe) { + dwe->PostEventMS(&EV_Activate, 0, this); + } + + // Allow collisions again now that we're in the death world + bInDeathwalkTransition = false; + Show(); // The player is visible in deathmode + + // Reset the player's health and spirit power + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" ); + inventory.ammo[ ammo_spiritpower ] = 0; + SetHealth( hhMath::hhMax( health, spawnArgs.GetInt("minResurrectHealth", "50") ) ); +} + +//============================================================================= +// +// hhPlayer::Event_AdjustSpiritPowerDeathWalk +// +//============================================================================= +void hhPlayer::Event_AdjustSpiritPowerDeathWalk() { + if( !IsDeathWalking() ) { + return; + } + + // Increase deathwalk power + int power = GetDeathWalkPower(); + power += spawnArgs.GetInt( "deathWraithPowerAmount" ); + SetDeathWalkPower( power ); + + // Check if deathWalkPower has exceeded the maximum + if ( deathWalkPower >= spawnArgs.GetInt( "deathWalkPowerMax", "1000") ) { + DeathWalkSuccess(); + return; + } + + CancelEvents( &EV_AdjustSpiritPowerDeathWalk ); + PostEventSec( &EV_AdjustSpiritPowerDeathWalk, spawnArgs.GetFloat("deathWalkDeathPowerIncreaseRate") ); +} + +//============================================================================= +// +// hhPlayer::DeathWalkSuccess +// +// Called when deathwalk timer runs out or we run out of spirit power +//============================================================================= +void hhPlayer::DeathWalkSuccess() { + CancelEvents( &EV_AdjustSpiritPowerDeathWalk ); + bDeathWalkStage2 = true; + + idEntity *dw_deathWalkResult = gameLocal.FindEntity("dw_deathWalkSuccess"); + if (dw_deathWalkResult) { //if we have a result, activate it. + dw_deathWalkResult->ProcessEvent(&EV_Activate, this); + } +} + +//============================================================================= +// +// hhPlayer::ReallyKilled +// +// Called when the player should be truly killed +// Funnels through Killed(), but disabled the deathwalk functionality +//============================================================================= + +void hhPlayer::ReallyKilled( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !IsSpiritOrDeathwalking() ) { + return; + } + + bDeathWalk = false; + bSpiritWalk = false; + + bReallyDead = true; + + // Turn off low-pass effect + if (!gameLocal.isClient) { //rww + if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers + gameLocal.SpiritWalkSoundMode( false ); + gameLocal.DialogSoundMode( false ); + } + } + + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + + Killed( inflictor, attacker, damage, dir, location ); +} + +//============================================================================= +// +// hhPlayer::Event_SpawnDeathWraith +// +// Spawns in a special type of wraith that is only visible in deathwalk +//============================================================================= + +void hhPlayer::Event_SpawnDeathWraith() { + idDict args; + idEntity *ent; + int maxDeathWalkWraiths = spawnArgs.GetInt( "deathWalkMaxWraiths" ); + + if ( !IsDeathWalking() ) { // Don't spawn in a wraith if the player has resurrected + return; + } + + float angle = hhMath::PI * gameLocal.random.RandomFloat(); + idVec3 startPoint = idVec3( hhMath::Sin(angle) * 600.0f, hhMath::Cos(angle) * 600.0f, 0.0f ) - GetAxis()[2] * 100.0f; + + args.Clear(); + args.SetVector( "origin", GetEyePosition() + startPoint ); + args.SetMatrix( "rotation", idAngles( 0.0f, (-viewAxis[0]).ToYaw(), 0.0f ).ToMat3() ); + ent = gameLocal.SpawnObject( spawnArgs.GetString("def_deathWraith"), &args ); + if ( ent ) { + hhDeathWraith *wraith = static_cast< hhDeathWraith * > ( ent ); + wraith->SetEnemy( this ); + } + + // Check the number of deathwraiths in the world + int count = 0; + for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent && ent->IsType( hhDeathWraith::Type ) ) { + count++; + } + } + + if ( count < maxDeathWalkWraiths ) { + PostEventMS( &EV_SpawnDeathWraith, 0 ); + } +} + +//============================================================================= +// +// hhPlayer::GetDeathwalkEnergyDestination +// +//============================================================================= +idEntity *hhPlayer::GetDeathwalkEnergyDestination() { + return gameLocal.FindEntityOfType(hhDeathWalkProxy::Type, NULL); +} + +//============================================================================= +// +// hhPlayer::DeathWalkDamagedByWraith +// +//============================================================================= +void hhPlayer::DeathWalkDamagedByWraith(idEntity *attacker, const char *damageType) { + Damage( attacker, attacker, vec3_origin, damageType, spawnArgs.GetFloat( "deathWalkWraithDamage", "10" ), INVALID_JOINT ); + + CancelEvents( &EV_SpawnDeathWraith ); + PostEventMS( &EV_SpawnDeathWraith, 0 ); +} + +//============================================================================= +// +// hhPlayer::KilledDeathWraith +// +//============================================================================= + +void hhPlayer::KilledDeathWraith( void ) { + CancelEvents( &EV_SpawnDeathWraith ); + PostEventMS( &EV_SpawnDeathWraith, 0 ); +} + +//============================================================================= +// +// hhPlayer::DeathWraithEnergyArived +// +//============================================================================= + +void hhPlayer::DeathWraithEnergyArived(bool energyHealth) { + if (energyHealth) { + const char *healthAmount = spawnArgs.GetString("deathWraithHealthAmount"); + Give( "health", healthAmount ); + } + else { + Give( "ammo_spiritpower", spawnArgs.GetString( "deathWraithSpiritAmount" ) ); + } + + // Bump the deathwalk power up a bit so the player lowers faster + int power = GetDeathWalkPower(); + power += spawnArgs.GetInt( "deathWalkPowerEnergyBoost", "100" ); + SetDeathWalkPower( power ); +} + +//============================================================================= +// +// hhPlayer::Resurrect +// +//============================================================================= + +void hhPlayer::Resurrect( void ) { + + bDeathWalk = false; + + possessionFOV = 0.0f; + + PostEventSec( &EV_AllowDamage, 3 ); // Re-enable damage in 3 seconds + fl.noknockback = false; // Restore knockback ability + + // Cancel the impending translation to deathworld (only happens if the player resurrect cheats early) + CancelEvents( &EV_PrepareForDeathWorld ); + CancelEvents( &EV_EnterDeathWorld ); + CancelEvents( &EV_SpawnDeathWraith ); + + DisableEthereal(); + + // Reset the view overlay + playerView.SetViewOverlayTime( 0, true ); + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); + + //rww - deathwalk position is restored from the player now. + RestorePlayerLocationFromDeathwalk(deathwalkLastOrigin, deathwalkLastBBoxAxis, deathwalkLastViewAxis[0], deathwalkLastViewAngles); + + deathwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 ); + + ProcessEvent( &EV_RechargeHealth ); + ProcessEvent( &EV_RechargeRifleAmmo ); + + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + + // TODO: Build in an autoimmune time? + lastResurrectTime = gameLocal.GetTime(); + + // Radius blast (doesn't damage enemies / objects, only shoves them back) + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_resurrect_damage") ); + + Show(); // Ensure that the player is visible when they resurrect + + // trigger the 'DeathWalkExited' entity + idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkExited"); + if (dwe) { + dwe->PostEventMS(&EV_Activate, 0, this); + } + + // bg: Visual effect for returning from DeathWalk uses landing camera movement. + landChange = -10; + landTime = gameLocal.time; + + //rww - deactivate dw portal and remove cam target + idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" ); + if (dwPortal && dwPortal->IsActive()) { + dwPortal->cameraTarget = NULL; //reset the camera target + dwPortal->PostEventMS(&EV_Activate, 0, dwPortal); + } + idEntity *dwCamTarget = gameLocal.FindEntity( "dw_deathLocation" ); + if (dwCamTarget) { + dwCamTarget->PostEventMS(&EV_Remove, 50); //make sure this is removed after the portal has stopped. + } +} + +/* +============ +hhPlayer::IsWallWalking +============ +*/ +bool hhPlayer::IsWallWalking( void ) const { + return( physicsObj.IsWallWalking() ); +} + +/* +============ +hhPlayer::WasWallWalking +============ +*/ +bool hhPlayer::WasWallWalking( void ) const { + return( physicsObj.WasWallWalking() ); +} + +/* +============ +hhPlayer::MangleControls +============ +*/ +void hhPlayer::MangleControls( usercmd_t *cmd ) { + // When frozen in a cinematic, we want to restrict any movement and disallow many features + if (guiWantsControls.IsValid()) { + if (cmd->buttons & BUTTON_ATTACK_ALT) { + guiWantsControls->LockedGuiReleased(this); + guiWantsControls = NULL; // release + } + else { + guiWantsControls->PlayerControls(cmd); + } + cmd->forwardmove = 0; + cmd->rightmove = 0; + cmd->upmove = 0; + cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT); + cmd->impulse = 0; + } + else if (InCinematic()) { + cmd->forwardmove = 0; + cmd->rightmove = 0; + cmd->upmove = 0; + cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT); + cmd->impulse = 0; + } +} + +/* +============ +hhPlayer::GetPilotInput + +Called from PilotVehicleInterface +============ +*/ +void hhPlayer::GetPilotInput( usercmd_t& pilotCmds, idAngles& pilotViewAngles ) { + pilotCmds = gameLocal.usercmds[ entityNumber ]; + pilotViewAngles = DetermineViewAngles( pilotCmds, cmdAngles ); +} + +/* +============== +hhPlayer::Think + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhPlayer::Think( void ) { + renderEntity_t *headRenderEnt; + + UpdatePossession(); + + UpdatePlayerIcons(); + + // latch button actions + oldButtons = usercmd.buttons; + + // grab out usercmd + usercmd_t oldCmd = usercmd; + usercmd = gameLocal.usercmds[ entityNumber ]; + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return; + } + + //HUMANHEAD rww - keep air attacker timer refreshed + if (!AI_ONGROUND && airAttackerTime > gameLocal.time) { + airAttackerTime = gameLocal.time + 300; + } + + // HUMANHEAD pdm: Depending on our mode, might want to modify the input + MangleControls( &usercmd ); + // HUMANHEAD END + + // 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 ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + // 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 ); + } + + // zooming + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) { + if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) { + zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() ); + } else { + zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() ); + } + } + + if ( g_fov.IsModified() ) { + idEntity *weaponEnt = weapon.GetEntity(); + if ( ! ( weaponEnt && + weaponEnt->IsType( hhWeaponZoomable::Type ) && + reinterpret_cast (weaponEnt)->IsZoomed() ) ) + { + GetZoomFov().Init( gameLocal.GetTime(), 0.0f, CalcFov(true), g_fov.GetInteger() ); + g_fov.ClearModified(); + } + } + + // 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.GetEntity() ) { + weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); + } + + EvaluateControls(); + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + CopyJointsFromBodyToHead(); + } + + //HUMANHEAD: aob - added vehicle check. Vehicle moves us + if( !InVehicle() ) { + Move(); + } + //HUMANHEAD END + + if ( !g_stopTime.GetBool() ) { + //HUMANHEAD: aob - changed heath check to IsDead check. Player needs to touch triggers when deathwalking. + if ( !noclip && !spectating && !IsDead() && !IsHidden() ) { + TouchTriggers(); + } + + UpdateLighter(); + + // update GUIs, Items, and character interactions + UpdateFocus(); + + UpdateLocation(); + + // update player script + UpdateScript(); + + // service animations + if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) { + 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 + AI_PAIN = false; + } + + // 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 ) { + if (!gameLocal.isClient) { + UpdateSpectating(); + } + //HUMANHEAD: aob - changed heath check to IsDead check. Player needs to update weapon when deathwalking + } else if ( !IsDead() && !bFrozen ) { //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + if ( !gameLocal.isClient || weapon.GetEntity()) { + UpdateWeapon(); + } + } + + if (InVehicle()) { + UpdateHud( GetVehicleInterfaceLocal()->GetHUD() ); + } + else { + UpdateHud( hud ); + } + + UpdateDeathSkin( false ); + + if ( gameLocal.isMultiplayer ) { + DrawPlayerIcons(); + } + + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + + if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) { + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } + // never cast shadows from our first-person muzzle flashes + renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber; + } + + if ( !g_stopTime.GetBool() ) { + UpdateAnimation(); + Present(); + UpdateDamageEffects(); + if (gameLocal.isMultiplayer) { //rww + UpdateWounds(); + } + LinkCombat(); + playerView.CalculateShake(); + } + + if ( g_showEnemies.GetBool() ) { + idActor *ent; + int num = 0; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); + num++; + } + gameLocal.Printf( "%d: enemies\n", num ); + } +} + +/* +============== +hhPlayer::AdjustBodyAngles +============== +*/ +void hhPlayer::AdjustBodyAngles( void ) { + if (InVehicle()) { + return; + } + + if (bClampYaw) { //rww - for slabs + //first, clamp the viewangles while bound to a slab + if (maxRelativePitch >= 0.0f) { + if (untransformedViewAngles.pitch > maxRelativePitch) { + untransformedViewAngles.pitch = maxRelativePitch; + SetViewAngles(untransformedViewAngles); + } + else if (untransformedViewAngles.pitch < -maxRelativePitch) { + untransformedViewAngles.pitch = -maxRelativePitch; + SetViewAngles(untransformedViewAngles); + } + } + + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, mat3_identity ); //no leg offset while slabbed + +#if 0 //old headlook code + if (head.IsValid() && head.GetEntity()) { //has a head + idVec3 origin; + idMat3 axis; + + head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); //make sure it is bolted up to date + + if (GetMasterPosition(origin, axis)) { + idAngles masterAngles = axis.ToAngles(); //bound angles + idAngles ang; + idMat3 rot; + + //determine our "look" angles + ang.yaw = viewAngles.yaw-masterAngles.yaw; + ang.pitch = viewAngles.pitch; + ang.roll = 0.0f; + + ang = ang.Normalize180(); + ang *= 0.5f; //scale the angles down so the head doesn't go completely sideways or anything + + //set the yaw only + animator.SetJointAxis(headJoint, JOINTMOD_WORLD, idAngles(0.0f, ang.yaw, 0.0f).ToMat3()); + ang.yaw = 0.0f; + + hhMath::BuildRotationMatrix(DEG2RAD(-ang.pitch), 0, rot); //create a rotation matrix for the pitch + + //get the local axis to multiply it by the pitch rotation matrix + animator.GetJointLocalTransform(headJoint, gameLocal.time, origin, axis); + + //use the multiplied axis + animator.SetJointAxis(headJoint, JOINTMOD_LOCAL, rot*axis); //point head in proper direction + } + } +#endif + } + else { +#if 0 //old headlook code + //rww - reset head joint override when not on a slab + animator.SetJointAxis( headJoint, JOINTMOD_LOCAL, idAngles( 0.0f, 0.0f, 0.0f ).ToMat3() ); +#endif + idPlayer::AdjustBodyAngles(); + } +} + +/* +============== +hhPlayer::Move + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhPlayer::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( GetStepHeight() );//HUMANHEAD: aob + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( bInDeathwalkTransition ) { // In between life and death, don't allow ragdoll to collide with player + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NORMAL ); + } else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + if (gameLocal.isMultiplayer) { // HUMANHEAD rww - contents 0 because we use a dead body proxy in mp + physicsObj.SetContents(0); + } + else { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + } //HUMANHEAD END + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView ) { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_FREEZE ); + } else if ( bFrozen && !IsSpiritOrDeathwalking() ) { // HUMANHEAD: freeze support + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating ) { + physicsObj.SetClipMask( MASK_SPECTATOR ); + } else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + physicsObj.SetClipMask( MASK_DEADSOLID ); + // HUMANHEAD cjr: allow spirit walk + } else if ( IsSpiritOrDeathwalking() ) { + physicsObj.SetClipMask( MASK_SPIRITPLAYER ); + // HUMANHEAD END + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + physicsObj.SetPlayerInput( usercmd, viewAngles ); + + //HUMANHEAD: aob - moved down a few lines + // FIXME: physics gets disabled somehow + //BecomeActive( TH_PHYSICS ); + //RunPhysics(); + //HUMANHEAD END + + // update our last valid AAS location for the AI + SetAASLocation(); + + if ( spectating ) { + newEyeOffset = 0.0f; + // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override + } else if ( IsDead() ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + //AOB: camera does smoothing + SetEyeHeight( newEyeOffset ); + } + + // HUMANHEAD aob + float camRotScale = p_camRotRateScale.GetFloat(); + //rww - if on a wallwalk mover, increase the scale + if (GetPhysics() == &physicsObj && physicsObj.GetGroundTrace().fraction < 1.0f) { + const trace_t &grTr = physicsObj.GetGroundTrace(); + if (grTr.c.entityNum >= 0 && grTr.c.entityNum < MAX_GENTITIES && gameLocal.entities[grTr.c.entityNum] && gameLocal.entities[grTr.c.entityNum]->IsType(hhMover::Type) && grTr.c.material && grTr.c.material->GetSurfaceType() == SURFTYPE_WALLWALK) { + camRotScale *= 5.0f; + } + } + cameraInterpolator.Setup( camRotScale, IT_VariableMidPointSinusoidal );//Also set in constructor + + // moved here to allow weapons physics get correct eyeOffset + BecomeActive( TH_PHYSICS ); //rww - prevent the bug that id so lovingly left for us + RunPhysics(); + // HUMANHEAD END + + // HUMANHEAD pdm: Experimental: allow player to jump up onto wallwalk +#define WALLWALK_EXPERIMENT 0 +#if WALLWALK_EXPERIMENT + if (!physicsObj.IsWallWalking() && physicsObj.HasJumped()) { + trace_t trace; + memset(&trace, 0, sizeof(trace)); + idVec3 start, end; + start = physicsObj.GetOrigin(); + end = start + idVec3(0,0,100); //fixme: jumpheight + if (gameLocal.clip.TracePoint( trace, start, end, MASK_SOLID, this )) { + if (gameLocal.GetMatterType(trace, NULL) == SURFTYPE_WALLWALK) { + gameLocal.Printf("Trying to flip over for wallwalk\n"); + + // Clear velocity, so won't miss wallwalk above + physicsObj.SetLinearVelocity(vec3_origin); + + SetGravity(-trace.c.normal * DEFAULT_GRAVITY); + ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false ); + ProcessEvent( &EV_OrientToGravity, (int)true ); + + physicsObj.IsWallWalking( true ); + +/* idVec3 flippedOrigin = trace.endpos; + idMat3 flippedAxis; + flippedAxis[0] = GetAxis()[0]; + flippedAxis[2] = trace.c.normal; + flippedAxis[1] = flippedAxis[0].Cross(flippedAxis[2]); + +// cameraInterpolator.SetInterpolationType(flags); +// cameraInterpolator.SetTargetEyeOffset(idealEyeOffset, flags); +// cameraInterpolator.SetTargetPosition(idealPosition, flags); + + // Flip the physics object + physicsObj.SetOrigin(flippedOrigin); + physicsObj.SetAxis(flippedAxis); + physicsObj.CheckWallWalk(true);*/ + } + } + } +#endif + // HUMANHEAD END + + if ( noclip || gameLocal.inCinematic ) { + AI_CROUCH = false; + AI_ONGROUND = false; + AI_ONLADDER = false; + AI_JUMP = false; + AI_REALLYFALL = true; //HUMANHEAD rww - well, it fits with ONGROUND false + } else { + AI_CROUCH = physicsObj.IsCrouching(); + AI_ONGROUND = physicsObj.HasGroundContacts(); + AI_ONLADDER = physicsObj.OnLadder(); + AI_JUMP = physicsObj.HasJumped(); + //HUMANHEAD rww + if (!physicsObj.IsInwardGravity()) { + AI_REALLYFALL = !AI_ONGROUND; + } + else if (!AI_ONGROUND) { //for inward gravity zones, do an extra ground check when in-air + AI_REALLYFALL = !physicsObj.ExtraGroundCheck(); + } + //HUMANHEAD END + + // 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::Type ) ) { + 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_walkspeed.GetFloat(); + } else { + // give em a push in the direction they're going + vel *= 1.1f; + } + physicsObj.SetLinearVelocity( vel ); + } + } + + if ( AI_JUMP ) { + // bounce the view weapon + 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; + } + + BobCycle( pushVelocity ); + if ( !noclip ) { // HUMANHEAD: Only crashland if not spiritwalking or not deathwalking + CrashLand( oldOrigin, oldVelocity ); + } + + //HUMANHEAD: aob - put this into helper func + if (!gameLocal.isClient) { + if( IsWallWalking() && !WasWallWalking() ) { + wallwalkSoundController.StartSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, SSF_LOOPING, false ); + } else if( !IsWallWalking() && WasWallWalking() ) { + wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false ); + } + } + physicsObj.WasWallWalking( IsWallWalking() ); //rww - moved here + //HUMANHEAD END +} + +/* +=============== +hhPlayer::ShouldTouchTrigger +=============== +*/ +bool hhPlayer::ShouldTouchTrigger( idEntity* entity ) const { + if( !entity ) { + return false; + } + + if ( entity->fl.onlySpiritWalkTouch ) { // cjr - Trigger can only be touched by spirit + return IsSpiritWalking(); + } + + if( IsSpiritOrDeathwalking() && !entity->fl.allowSpiritWalkTouch ) { // Trigger can be touched by either physical or spirit + return false; // jrm - reversed logic + } + + return true; +} + +/* +=============== +hhPlayer::HandleSingleGuiCommand +=============== +*/ +bool hhPlayer::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("guilockplayer") == 0) { + //Now locked in place: + // manglecontrols() will route controls to current gui focus + if (entityGui->IsType(hhConsole::Type)) { + guiWantsControls = static_cast(entityGui); + } + return true; + } + + src->UnreadToken(&token); + return idPlayer::HandleSingleGuiCommand(entityGui, src); +} + +void hhPlayer::SetOverlayGui(const char *guiName) { + if (guiName && *guiName && guiOverlay == NULL) { + guiOverlay = uiManager->FindGui(guiName, true); + if (guiOverlay) { + guiOverlay->Activate(true, gameLocal.time); + } + } + else { + guiOverlay = NULL; + } +} + +/* +============ +hhPlayer::ForceWeapon +nla: used to instantly force the spirit weapon, without lowering and raising +============ +*/ +void hhPlayer::ForceWeapon( int weaponNum ) { + const char * weaponDef; + hhWeapon * newWeapon; + + if (spectating) { //rww - if spectating this is bad. + gameLocal.Error("hhPlayer::ForceWeapon called on spectator. (client %i)", entityNumber); + } + + // HUMANHEAD mdl: Special case - coming out of spirit mode when no physical weapon is available + if ( weaponNum == -1 ) { + idealWeapon = 0; + currentWeapon = -1; + return; + } + + if ( weaponNum < 1 ) { + gameLocal.Warning( "Error: Illegal weapon num passed: %d\n", weaponNum ); + } + + weaponDef = GetWeaponName( weaponNum ); + + newWeapon = SpawnWeapon( weaponDef ); + + weapon = newWeapon; + + idealWeapon = currentWeapon = weaponNum; + + animPrefix = weaponDef; + animPrefix.Strip( "weaponobj_" ); +} + + +/* +============ +hhPlayer::ForceWeapon +============ +*/ +void hhPlayer::ForceWeapon( hhWeapon *newWeapon ) { + int weaponNum; + + + weapon = newWeapon; + + weaponNum = GetWeaponNum( weapon->GetDict()->GetString( "classname" ) ); + + idealWeapon = currentWeapon = weaponNum; + +} + +/* +=========== +hhPlayer::Teleport + +HUMANHEAD cjr: Now calls TeleportNoKillBox, then applies a kill box +============ +*/ +void hhPlayer::Teleport( const idVec3& origin, const idAngles& angles, idEntity *destination ) { + //pdm - converted this to call our other version so it doesn't unalign the clip model + TeleportNoKillBox( origin, mat3_identity, angles.ToForward(), angles ); + + // mdl: Moved here from TeleportNoBox, so player can't avoid falling damage by toggling spirit mode quickly + physicsObj.SetLinearVelocity( vec3_origin ); + physicsObj.SetKnockBack( 250 ); // Slow the player down after a teleport for a moment + + // kill anything at the new position + gameLocal.KillBox( this, destination != NULL ); + + teleportEntity = destination; +} + +/* +=========== +hhPlayer::Teleport + +HUMANHEAD aob: Needed for coming back from deathwalk +============ +*/ +void hhPlayer::Teleport( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles, idEntity *destination ) { + TeleportNoKillBox( origin, bboxAxis, viewDir, newUntransformedViewAngles ); + + // kill anything at the new position + gameLocal.KillBox( this, destination != NULL ); + + teleportEntity = destination; +} + +/* +=========== +hhPlayer::TeleportNoKillBox + +HUMANHEAD cjr: Called from SpiritProxy +============ +*/ +void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles ) { + DisableIK(); + + SetOrientation( origin, bboxAxis, viewDir, newUntransformedViewAngles ); + + CancelEvents( &EV_ResetGravity ); + ProcessEvent( &EV_ResetGravity ); // Guarantee that gravity is instantly reset after this teleport + + EnableIK(); +} + +/* +=========== +hhPlayer::TeleportNoKillBox + +HUMANHEAD cjr: Teleport, but don't telefrag anything +============ +*/ + +void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis ) { + TeleportNoKillBox( origin, bboxAxis, GetAxis()[0], GetUntransformedViewAngles() ); +} + +/* +===================== +hhPlayer::Possess + +//HUMANHEAD: cjr - this is the entry point for possession. +===================== +*/ +void hhPlayer::Possess( idEntity* possessor ) { + bPossessed = true; + + /* TODO: + - needs a new spirit proxy (a little hidden object just as storage that is linked to the possessed human) + - disallow the player from returning until the possessed tommy has been "killed" + + + possessed tommy: + - attacks whatever + - health ticking down -- when it reaches zero, ragdoll and instantly kill the spirit player + - if killed by the spirit bow, then the wraith is removed (seperate health?) + */ + + // If we're spirit walking when we're possessed, snap back to our body for a moment, then get thrown back out. + //TODO this isn't ideal + if ( IsSpiritOrDeathwalking() ) { + DisableEthereal(); + } + + // Must allow spirit walking at this point + HH_ASSERT( bAllowSpirit ); + + // Force the player into spirit and thrust them backwards + StartSpiritWalk( true, true ); + + // test: spawn in a possessed version + // need: effects here...flash or whatever + possessedTommy = (hhPossessedTommy *)gameLocal.SpawnObject( "monster_possessed_tommy", NULL ); + + if ( possessedTommy.IsValid() ) { // Copy player stats to the possessed Tommy + /*if ( IsSpiritOrDeathwalking() ) { //TODO mdl: Remove this if we go with wraiths knocking players back to their body + // This should means we were called by + possessedTommy->SetOrigin( spiritProxy->GetOrigin() ); + possessedTommy->SetAxis( spiritProxy->GetAxis() ); + } else*/ { + possessedTommy->SetOrigin( GetOrigin() ); + possessedTommy->SetAxis( GetAxis() ); + } + possessedTommy->SetPossessedProxy( spiritProxy.GetEntity() ); + } +} + +/* +===================== +hhPlayer::Unpossess + +//HUMANHEAD: cjr +=====================*/ + +void hhPlayer::Unpossess() { + bPossessed = false; + + possessedTommy = NULL; +/* + idDict args; + idEntity *ent; + + playerView.SetViewOverlayMaterial( NULL ); + possessionFOV = 0; // Restore the original FOV + + // Spawn a wraith to fly out of the player's origin + args.Clear(); + args.SetVector( "origin", GetOrigin() + GetEyePosition() + GetAxis()[0] * 50 ); + args.SetMatrix( "rotation", GetAxis() ); +//TODO: Externalize this monster_wraith + args.Set( "classname", "monster_wraith" ); + + gameLocal.SpawnEntityDef( args, &ent ); + ((hhWraith *)ent)->SetEnemy( this ); + + // TODO: Unpossessed by portalling, so kill the wraith +*/ +} + +//============================================================================= +// +// hhPlayer::CanBePossessed +// +// Players can only be possessed if they are are not currently possessed +//============================================================================= + +bool hhPlayer::CanBePossessed( void ) { + if ( godmode || noclip || !bAllowSpirit ) { // Don't possess if in god mode or noclipping + return false; + } + + return !(IsPossessed() || InVehicle() || IsSpiritOrDeathwalking() ); +} + +//============================================================================= +// +// hhPlayer::PossessKilled +// +// Killed when possessed (spirit power ran out) +// +// - Ragdoll the possessed body +// - Return to the location of the possessed tommy and die +//============================================================================= + +void hhPlayer::PossessKilled( void ) { + if ( possessedTommy.IsValid() ) { + possessedTommy->PostEventMS( &EV_Remove, 0 ); + } + + Unpossess(); + StopSpiritWalk(); + Kill( 0, 0 ); // FIXME: Some other way to kill self (instead of suicide?) +} + +//============================================================================= +// +// hhPlayer::Portalled +// +// Player just portalled +//============================================================================= + +void hhPlayer::Portalled( idEntity *portal ) { + if ( talon.IsValid() && talon->GetBindMaster() != this ) { // If Talon isn't bound to Tommy, portal him with the player + talon->Portalled( portal ); + } + + if (gameLocal.isClient) { //HUMANHEAD rww - compensate for angle jump + bBufferNextSnapAngles = true; + smoothedAngles = viewAngles; + } + smoothedFrame = 0; //HUMANHEAD rww - skip smoothing on the portalled frame + walkIK.InvalidateHeights(); //HUMANHEAD rww - don't try to interpolate ik positions between two equal planes on either side of a portal + if (gameLocal.isServer) { //HUMANHEAD rww - send an unreliable event in the coming snapshot + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.WriteBits(gameLocal.GetSpawnId(portal), 32); + ServerSendEvent(EVENT_PORTALLED, &msg, false, -1, -1, true); //only send it to the client who portalled + } +} + +/* +================ +hhPlayer::UpdateModelTransform +================ +*/ +void hhPlayer::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if( GetPhysicsToVisualTransform(origin, axis) ) { + //HUMANHEAD: aob + GetRenderEntity()->axis = axis; + idVec3 absOrigin = TransformToPlayerSpaceNotInterpolated( origin ); //rww - don't use interpolated origin + GetRenderEntity()->origin = absOrigin; + //HUMANHEAD END + } else { + //HUMANHEAD: aob + GetRenderEntity()->axis = GetAxis(); + GetRenderEntity()->origin = GetOrigin(); + //HUMANHEAD END + } +} + +/* +==================== +hhPlayer::CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +Takes possession FOV into account + +HUMANHEAD cjr +==================== +*/ +float hhPlayer::CalcFov( bool honorZoom ) { + float fov; + + // HUMANHEAD mdl: Refactored this to work properly with possessionFOV. Being zoomed in will ignore possessionFOV, however. + idEntity *weaponEnt = weapon.GetEntity(); + if ( possessionFOV > 0.0f && + ! ( weaponEnt && + weaponEnt->IsType( hhWeaponZoomable::Type ) && + reinterpret_cast (weaponEnt)->IsZoomed() ) ) + { + fov = possessionFOV; + } else if ( IsDeathWalking() ) { + fov = spawnArgs.GetFloat("deathwalkFOV", "90"); + } else if ( pm_thirdPerson.GetBool() ) { + fov = g_fov.GetFloat(); + } else if ( InCinematic() ) { + fov = cinematicFOV.GetCurrentValue(gameLocal.time); + } else { + fov = zoomFov.GetCurrentValue(gameLocal.time); + } + + //HUMANHEAD: aob + fov = hhMath::ClampFloat( 1.0f, 179.0f, fov ); + //HUMANHEAD END + + return fov; +} + +/* +=============== +hhPlayer::EnterVehicle +=============== +*/ +void hhPlayer::EnterVehicle( hhVehicle* vehicle ) { + if (!gameLocal.isClient) { //HUMANHEAD PCF rww 05/04/06 - do not do the check on the client, just listen to what the snapshot says. + if (!vehicle->WillAcceptPilot(this)) { + return; + } + } + + // Cancel any pending damage allowing events, since we'll be turning damage off + CancelEvents(&EV_AllowDamage); + + // Set shuttle view + if (entityNumber == gameLocal.localClientNum) { //rww + renderSystem->SetShuttleView( true ); + } + + // Move eye to proper height + SetEyeHeight( vehicle->spawnArgs.GetFloat("pilot_eyeHeight") ); + + // Turn off any illegal behaviors + if( IsSpiritWalking() ) { + StopSpiritWalk(); + } + + LighterOff(); + + // nla - Remove any offset cause by jumping. (Fixes the jumping and getting in the shuttle bug) + SetViewBob( vec3_origin ); + + // CJR: Inform Talon that the player has entered a vehicle + if( talon.IsValid() ) { + talon->OwnerEnteredVehicle(); + } + + ShouldRemainAlignedToAxial( false ); + + cameraInterpolator.SetInterpolationType( IT_None ); + SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles() ); + //Need to clear untransformedViewAxis so cameraInterpolator doesn't do any unnessacary transforms + SetUntransformedViewAxis( mat3_identity ); + + //Hack + cameraInterpolator.Reset( GetOrigin(), mat3_identity[2], EyeHeightIdeal() ); + + idPlayer::EnterVehicle( vehicle ); +} + +/* +=============== +hhPlayer::ExitVehicle + +This should only be called from vehicle +=============== +*/ +void hhPlayer::ExitVehicle( hhVehicle* vehicle ) { + + // Allow model in player's view again + if (vehicle) { + vehicle->GetRenderEntity()->suppressSurfaceInViewID = 0; + } + + // Set shuttle view off + if (entityNumber == gameLocal.localClientNum) { //rww + renderSystem->SetShuttleView( false ); + } + + ShouldRemainAlignedToAxial( true ); + + // CJR: Inform Talon that the player has left a vehicle + if( talon.IsValid() ) { + talon->OwnerExitedVehicle(); + } + + idPlayer::ExitVehicle( vehicle ); + + buttonMask |= BUTTON_ATTACK_ALT; //HUMANHEAD bjk + + // Reset the animPrefix + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + animPrefix.Strip( "weaponobj_" ); + + if (vehicle) { + SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles().Normalize180() ); + } + cameraInterpolator.SetInterpolationType( IT_VariableMidPointSinusoidal ); +} + +// +// ResetClipModel() +// +// HUMANHEAD: aob +// +void hhPlayer::ResetClipModel() { + //Needed for touching triggers. Our bbox is its original size. + SetClipModel(); +} + +/* +=============== +hhPlayer::BecameBound +=============== +*/ +void hhPlayer::BecameBound(hhBindController *b) { + if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp + weapon->Hide(); + } +} + +/* +=============== +hhPlayer::BecameUnbound +=============== +*/ +void hhPlayer::BecameUnbound(hhBindController *b) { + if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp + weapon->Show(); + } +} + +void hhPlayer::GetLocationText( idStr &locationString ) { + idLocationEntity* locationEntity = gameLocal.LocationForPoint( GetEyePosition() ); + if( locationEntity ) { + locationString = locationEntity->GetLocation(); + } + else { + locationString = common->GetLanguageDict()->GetString( "#str_02911" ); + } +} + +void hhPlayer::UpdateLocation( void ) { + if( hud ) { + hud->SetStateBool("showlocations", developer.GetBool()); + if (developer.GetBool()) { + idStr locationString; + GetLocationText(locationString); + hud->SetStateString( "location", locationString.c_str() ); + hud->SetStateInt( "areanum", gameRenderWorld->PointInArea( GetEyePosition() )); + } + } +} + +/* +=============== +hhPlayer::FillDebugVars +=============== +*/ +void hhPlayer::FillDebugVars(idDict *args, int page) { + idStr text; + + switch(page) { + case 1: + args->SetInt("deathpower", GetDeathWalkPower()); + args->SetInt("spiritpower", GetSpiritPower()); + args->SetBool("AI_CROUCH", AI_CROUCH != 0); + args->SetBool("AI_ONGROUND", AI_ONGROUND != 0); + args->SetBool("AI_JUMP", AI_JUMP != 0); + args->SetBool("AI_SOFTLANDING", AI_SOFTLANDING != 0); + args->SetBool("AI_HARDLANDING", AI_HARDLANDING != 0); + args->SetBool("AI_FORWARD", AI_FORWARD != 0); + args->SetBool("AI_BACKWARD", AI_BACKWARD != 0); + args->SetBool("AI_STRAFE_LEFT", AI_STRAFE_LEFT != 0); + args->SetBool("AI_STRAFE_RIGHT", AI_STRAFE_RIGHT != 0); + args->SetBool("AI_ATTACK_HELD", AI_ATTACK_HELD != 0); + args->SetBool("AI_WEAPON_FIRED", AI_WEAPON_FIRED != 0); + args->SetBool("AI_JUMP", AI_JUMP != 0); + args->SetBool("AI_DEAD", AI_DEAD != 0); + args->SetBool("AI_PAIN", AI_PAIN != 0); + args->SetBool("AI_RELOAD", AI_RELOAD != 0); + args->SetBool("AI_TELEPORT", AI_TELEPORT != 0); + args->SetBool("AI_TURN_LEFT", AI_TURN_LEFT != 0); + args->SetBool("AI_TURN_RIGHT", AI_TURN_RIGHT != 0); + args->SetBool("AI_ASIDE", AI_ASIDE != 0); + args->SetBool("AI_REALLYFALL", AI_REALLYFALL != 0); //HUMANHEAD rww + args->SetBool("HasGroundContacts", physicsObj.HasGroundContacts()); + args->Set("animPrefix", animPrefix.c_str()); + break; + case 2: + args->Set("Physics Axis", GetPhysics()->GetAxis().ToAngles().ToString()); + args->Set("viewAxis", viewAxis.ToAngles().ToString()); + args->Set("cmdAngles", cmdAngles.ToString()); + args->Set("deltaViewAngles", GetDeltaViewAngles().ToString() ); + args->Set("viewAngles", viewAngles.ToString()); + args->Set("untransAngles", GetUntransformedViewAngles().ToString()); + args->Set("xPhysics Axis", GetPhysics()->GetAxis().ToString()); + args->Set("xviewAxis", viewAxis.ToString()); + args->Set("idealLegsYaw", va("%.1f", idealLegsYaw)); + args->SetInt("usercmd.forwardmove", usercmd.forwardmove); + args->SetInt("usercmd.rightmove", usercmd.rightmove); + args->SetInt("legsForward", legsForward); + + switch(physicsObj.GetWaterLevel()) { + case WATERLEVEL_NONE: text = "none"; break; + case WATERLEVEL_FEET: text = "feet"; break; + case WATERLEVEL_WAIST: text = "waist"; break; + case WATERLEVEL_HEAD: text = "head"; break; + } + args->Set("waterlevel", text); + text = collisionModelManager->ContentsName(physicsObj.GetWaterType()); + args->Set("watertype", text); + break; + case 3: + break; + } + idPlayer::FillDebugVars(args, page); +} + +// +// GetAimPosition() +// +idVec3 hhPlayer::GetAimPosition() const { + return GetEyePosition(); +} + +/* +=============== +hhPlayer::WriteToSnapshot +=============== +*/ +void hhPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + bool vehControlling = vehicleInterfaceLocal.ControllingVehicle(); + + msg.WriteBits(vehControlling, 1); + + //rww - our stuff is now intermingled with id's for more ideal send ordering + physicsObj.WriteToSnapshot( msg, vehControlling ); + + if (!vehControlling) { + //not syncing these causes denormalization/nan issues, FIXME + msg.WriteFloat(untransformedViewAngles[0]); + msg.WriteFloat(untransformedViewAngles[1]); + msg.WriteFloat(untransformedViewAngles[2]); + idCQuat q = untransformedViewAxis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + //rww - write cameraInterpolator + cameraInterpolator.WriteToSnapshot(msg, this); + + //still need delta angles + //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + //msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + } +#if 0 //for debugging differences in snapshot + else { + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteFloat(0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteFloat(0.0f, 4, 4); + + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteFloat(0.0f, 4, 4); + + msg.WriteFloat(0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteFloat(0.0f, 4, 4); + + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + msg.WriteDeltaFloat(0.0f, 0.0f); + } +#endif + + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + + msg.WriteShort( health ); + msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, 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( weapon.GetSpawnId(), 32 ); + msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteBits( lastHitToggle, 1 ); + msg.WriteBits( weaponGone, 1 ); + WriteBindToSnapshot( msg ); +//===================END OF ID DATA + msg.WriteShort(inventory.maxHealth); //rww - since maxhealth can go above 100 in mp now + + msg.WriteBits( spiritProxy.GetSpawnId(), 32 ); + + //rww - more spiritwalk stuff + msg.WriteBits(lastWeaponSpirit, 32); + msg.WriteBits(bSpiritWalk, 1); + + //not needed anymore + /* + msg.WriteBits( bShowProgressBar, 1 ); + msg.WriteFloat( progressBarValue ); + msg.WriteBits( progressBarState, 2 ); // possible values [0..2] + */ + + //rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo + //in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate) + //rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary? + //i don't think it's all that costly, but still. + if (weapon.IsValid()) { + ammo_t ammoType = weapon->GetAmmoType(); + if (ammoType > 0) { + msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS); + } + else { + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + } + ammoType = weapon->GetAltAmmoType(); + if (ammoType > 0) { + msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS); + } + else { + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + } + } + else { + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS); + } + + //need to sync buttonMask since we're using it for some state-based things + msg.WriteBits(buttonMask, 8); + + msg.WriteFloat(EyeHeight()); + + //HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside + //enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a + //vehicle on the client or not. + //msg.WriteBits(AI_VEHICLE, 1); + + //rww - hand stuff + //weaponHandState.WriteToSnapshot(msg); + msg.WriteBits(hand.GetSpawnId(), 32); + msg.WriteBits(handNext.GetSpawnId(), 32); + + //jsh - vehicle stuff + msg.WriteBits( vehicleInterfaceLocal.GetVehicleSpawnId(), 32 ); + msg.WriteBits( vehicleInterfaceLocal.GetHandSpawnId(), 32 ); + //vehicleInterfaceLocal.GetWeaponHandState()->WriteToSnapshot(msg); + + //rww - lighter sync validation + msg.WriteBits((lighterHandle != -1), 1); + + //rww - tractor + msg.WriteBits(fl.isTractored, 1); + + spiritwalkSoundController.WriteToSnapshot(msg); + //deathwalkSoundController.WriteToSnapshot(msg); + wallwalkSoundController.WriteToSnapshot(msg); + + //HUMANHEAD rww - leechgun energy ammo + //note - current weapon's ammo always sent now + //ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy"); + //msg.WriteBits(inventory.ammo[energyammo], ASYNC_PLAYER_INV_AMMO_BITS); +} + +/* +=============== +hhPlayer::ReadFromSnapshot +=============== +*/ +void hhPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, oldHealth, newIdealWeapon, weaponSpawnId; + bool newHitToggle, stateHitch; + + bool vehControlling = !!msg.ReadBits(1); + + if ( snapshotSequence - lastSnapshotSequence > 1 ) { + stateHitch = true; + } else { + stateHitch = false; + } + lastSnapshotSequence = snapshotSequence; + + oldHealth = health; + + physicsObj.ReadFromSnapshot( msg, vehControlling ); + + if (!vehControlling) { + //not syncing these causes denormalization/nan issues, FIXME + if (bBufferNextSnapAngles) { + idAngles n; + n[0] = msg.ReadFloat(); + n[1] = msg.ReadFloat(); + n[2] = msg.ReadFloat(); + BufferLoggedViewAngles(n); + untransformedViewAngles = n; + bBufferNextSnapAngles = false; + } + else { + untransformedViewAngles[0] = msg.ReadFloat(); + untransformedViewAngles[1] = msg.ReadFloat(); + untransformedViewAngles[2] = msg.ReadFloat(); + } + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + untransformedViewAxis = q.ToMat3(); + + //rww - read cameraInterpolator + cameraInterpolator.ReadFromSnapshot(msg, this); + + //deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + //deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + //deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + } +#if 0 //for debugging differences in snapshot + else { + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadFloat(); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadFloat(4, 4); + + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadFloat(4, 4); + + msg.ReadFloat(); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadFloat(4, 4); + + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + msg.ReadDeltaFloat(0.0f); + } +#endif + + deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + + health = msg.ReadShort(); + lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, 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 ); + spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + newHitToggle = msg.ReadBits( 1 ) != 0; + weaponGone = msg.ReadBits( 1 ) != 0; + ReadBindFromSnapshot( msg ); + +//===================END OF ID DATA + + inventory.maxHealth = msg.ReadShort(); //rww - since maxhealth can go above 100 in mp now + + idQuat quat; + + /* + if( spiritProxy.SetSpawnId( msg.ReadBits( 32 ) ) ) { + if (spiritProxy.GetEntity() && spiritProxy.GetEntity()->IsType(hhSpiritProxy::Type)) + { + spiritProxy.GetEntity()->ActivateProxy( this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles ); + } + }*/ + spiritProxy.SetSpawnId( msg.ReadBits( 32 ) ); + + //rww - more spiritwalk stuff + lastWeaponSpirit = msg.ReadBits(32); + + bool spiritWalking = !!msg.ReadBits(1); + if (spiritWalking != bSpiritWalk && (weapon.IsValid() || !spiritWalking)) { + bSpiritWalk = spiritWalking; + + LighterOff(); //make sure lighter is off when switching spiritwalk + + if (bSpiritWalk) { + if (gameLocal.localClientNum == entityNumber) { + if (hud) { + hud->HandleNamedEvent("SwitchToEthereal"); + } + gameLocal.SpiritWalkSoundMode( true ); + } + + // Set the player's skin to a glowy effect + SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") ); + SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made + + //put bow in proper state + SelectEtherealWeapon(); + } + else { + if (gameLocal.localClientNum == entityNumber) { + if (hud) { + hud->HandleNamedEvent("SwitchFromEthereal"); + } + gameLocal.SpiritWalkSoundMode( false ); + } + + SetSkinByName( NULL ); + } + } + + //not needed anymore + /* + // Handle progress bar + bool bBar = msg.ReadBits(1) != 0; + float value = msg.ReadFloat(); + int state = msg.ReadBits(2); + CL_UpdateProgress(bBar, value, state); + */ + + // if not a local client assume the client has all ammo types + if ( entityNumber != gameLocal.localClientNum ) { + for( i = 0; i < AMMO_NUMTYPES; i++ ) { + inventory.ammo[ i ] = 999; + } + } + + //rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo + //in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate) + //rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary? + //i don't think it's all that costly, but still. + int primAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); + int altAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); + if (entityNumber != gameLocal.localClientNum) { + //since we have our own ammo in the playerstate, don't want to stomp it + if (weapon.IsValid()) { + ammo_t ammoType = weapon->GetAmmoType(); + if (ammoType > 0) { + inventory.ammo[ammoType] = primAmmo; + } + ammoType = weapon->GetAltAmmoType(); + if (ammoType > 0) { + inventory.ammo[ammoType] = altAmmo; + } + } + } + + //need to sync buttonMask since we're using it for some state-based things + buttonMask = msg.ReadBits(8); + + SetEyeHeight(msg.ReadFloat()); + + //HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside + //enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a + //vehicle on the client or not. + //AI_VEHICLE = !!msg.ReadBits(1); + + //rww - hand stuff + //weaponHandState.ReadFromSnapshot(msg); + hand.SetSpawnId(msg.ReadBits(32)); + handNext.SetSpawnId(msg.ReadBits(32)); + + //HUMANHEAD PCF rww 05/04/06 - base this check on AI_VEHICLE and make sure the player is exited, + //even if the vehicle is not in the snapshot at this point. + if (!vehControlling && AI_VEHICLE) { //then exit on the client + if (vehicleInterfaceLocal.ControllingVehicle()) { + hhVehicle *veh = GetVehicleInterface()->GetVehicle(); + if (veh && veh->GetPilot() == this) { + veh->EjectPilot(); + } + } + ExitVehicle(NULL); //be extra safe in case vehicle is no longer around + } + + //jsh - vehicle stuff + int vehSpawnId = msg.ReadBits( 32 ); + //HUMANHEAD PCF rww 05/04/06 - base this check ON AI_VEHICLE, in case snapshot where controlling and + //where the vehicle is set do not exactly coincide. + vehicleInterfaceLocal.SetVehicleSpawnId( vehSpawnId ); + if( !AI_VEHICLE ) { + if (vehControlling) { + hhVehicle *veh = vehicleInterfaceLocal.GetVehicle(); + if (veh && veh->IsType(hhVehicle::Type)) { + //GetVehicleInterface()->TakeControl( vehicleInterfaceLocal.GetVehicle(), this ); + EnterVehicle( vehicleInterfaceLocal.GetVehicle() ); + } + } + } + + /* + if( vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) ) ) { + vehicleInterfaceLocal.GetHandEntity()->AttachHand( this, true ); + } + */ + vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) ); + //vehicleInterfaceLocal.GetWeaponHandState()->ReadFromSnapshot(msg); + + //rww - lighter sync validation + bool newLighterOn = !!msg.ReadBits(1); + if (newLighterOn != IsLighterOn()) { + ToggleLighter(); + } + + //rww - tractor + fl.isTractored = !!msg.ReadBits(1); + + spiritwalkSoundController.ReadFromSnapshot(msg); + //deathwalkSoundController.ReadFromSnapshot(msg); + wallwalkSoundController.ReadFromSnapshot(msg); + + //=========================================== start id code + if ( weapon.SetSpawnId( weaponSpawnId ) ) { + //HUMANHEAD rww - i am getting crashes here and the stack is all messed up, + //claiming that SetOwner is off into null, when supposedly the entity is not. + //rearranging this code so it's easier to see what's going on in a crash. + hhWeapon *weapEnt = weapon.GetEntity(); + if ( weapEnt ) { + // maintain ownership locally + weapEnt->SetOwner( this ); + } + currentWeapon = -1; + } + + //HUMANHEAD rww - but we do want ammo count for the leechgun on other players for prediction + //note - current weapon's ammo always sent now + //ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy"); + //inventory.ammo[energyammo] = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS); + + if ( oldHealth > 0 && health <= 0 ) { + if ( stateHitch ) { + // so we just hide and don't show a death skin + UpdateDeathSkin( true ); + } + // die + AI_DEAD = true; + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 ); + SetWaitState( "" ); + animator.ClearAllJoints(); + + //HUMANHEAD rww - don't want this + /* + if ( entityNumber == gameLocal.localClientNum ) { + playerView.Fade( colorBlack, 12000 ); + } + */ + //HUMANHEAD END + + fl.clientEvents = true; //client event hackery for non-critical cosmetic cleanups + PostEventMS(&EV_RespawnCleanup, 32); + fl.clientEvents = false; + StartRagdoll(); + physicsObj.SetMovementType( PM_DEAD ); + if ( !stateHitch ) { + //HUMANHEAD PCF rww 09/15/06 - female mp sounds + if (IsFemale()) { + StartSound( "snd_death_female", SND_CHANNEL_VOICE, 0, false, NULL ); + } + else { + //HUMANHEAD END + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + if ( weapon.GetEntity() ) { + weapon.GetEntity()->OwnerDied(); + } + + //HUMANHEAD rww + GetPhysics()->SetContents(0); + Hide(); + + //so camera can operate on client. + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + 10000; + //HUMANHEAD END + } else if ( oldHealth <= 0 && health > 0 ) { + // respawn + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + //HUMANHEAD rww + if (!spectating) { + Show(); + } + //HUMANHEAD END + } else if ( health < oldHealth && health > 0 ) { + if ( stateHitch ) { + lastDmgTime = gameLocal.time; + } else { + // damage feedback + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); + if ( def ) { + playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict ); + AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); + lastDmgTime = gameLocal.time; + } else { + common->Warning( "NET: no damage def for damage feedback '%s'\n", lastDamageDef ); + } + } + } + + // If the player is alive, restore proper physics object + if ( health > 0 && IsActiveAF() ) { + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + } + + if ( idealWeapon != newIdealWeapon ) { + if ( stateHitch ) { + weaponCatchup = true; + } + idealWeapon = newIdealWeapon; + UpdateHudWeapon(); + } + + if ( lastHitToggle != newHitToggle ) { + SetLastHitTime( gameLocal.realClientTime ); + } + //=========================================== end id code + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +hhPlayer::ServerReceiveEvent +================ +*/ +bool hhPlayer::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( 6 ); + PerformImpulse(impulse); + if (vehicleInterfaceLocal.ControllingVehicle()) { + hhVehicle *veh = vehicleInterfaceLocal.GetVehicle(); + if (veh) { + veh->DoPlayerImpulse(impulse); + } + } + return true; + } + default: { + return false; + } + } +} + +/* +================ +hhPlayer::WritePlayerStateToSnapshot +================ +*/ +void hhPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const { + idPlayer::WritePlayerStateToSnapshot(msg); + + //rww - extra playerView stuff + playerView.WriteToSnapshot(msg); + + //rww - scope view + msg.WriteBits(bScopeView, 1); + + //rww - zoom fov + msg.WriteFloat(zoomFov.GetDuration()); + msg.WriteFloat(zoomFov.GetEndValue()); + msg.WriteFloat(zoomFov.GetStartTime()); + msg.WriteFloat(zoomFov.GetStartValue()); + + //rww - view angle sensitivity + msg.WriteFloat(GetViewAnglesSensitivity()); +} + +/* +================ +hhPlayer::ReadPlayerStateFromSnapshot +================ +*/ +void hhPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) { + idPlayer::ReadPlayerStateFromSnapshot(msg); + + //rww - extra playerView stuff + playerView.ReadFromSnapshot(msg); + + //rww - scope and fov handling + bool canOverrideView = true; + if (weapon.IsValid() && weapon->IsType(hhWeaponZoomable::Type)) { //if we have a zoomed weapon, don't override with snapshot while the prediction fudge timer is on + hhWeaponZoomable *weap = static_cast(weapon.GetEntity()); + if (weap->clientZoomTime >= gameLocal.time) { + canOverrideView = false; + } + } + + bool scopeView = !!msg.ReadBits(1); + float zfDur = msg.ReadFloat(); + float zfEnd = msg.ReadFloat(); + float zfStT = msg.ReadFloat(); + float zfStV = msg.ReadFloat(); + + renderSystem->SetShuttleView( InVehicle() ); + + if (canOverrideView) { //if we are currently allowed to override with snapshot values, do so. + bScopeView = scopeView; + if (bScopeView != renderSystem->IsScopeView()) { + renderSystem->SetScopeView(bScopeView); + } + zoomFov.SetDuration(zfDur); + zoomFov.SetEndValue(zfEnd); + zoomFov.SetStartTime(zfStT); + zoomFov.SetStartValue(zfStV); + } + + //rww - view angle sensitivity + SetViewAnglesSensitivity(msg.ReadFloat()); +} + +/* +================ +hhPlayer::ClientPredictionThink +================ +*/ +void hhPlayer::ClientPredictionThink( void ) { + if (gameLocal.localClientNum != entityNumber && forcePredictionButtons) { + //used by some weapons which rely on prediction-based projectiles, to ensure we don't miss a launch + gameLocal.usercmds[ entityNumber ].buttons |= forcePredictionButtons; + if (gameLocal.isNewFrame) { + forcePredictionButtons = 0; + } + } + + idPlayer::ClientPredictionThink(); + + if (InVehicle()) { + UpdateHud( GetVehicleInterfaceLocal()->GetHUD() ); + } + else { + UpdateHud( hud ); + } +} + +/* +================ +hhPlayer::GetPhysicsToVisualTransform +================ +*/ +bool hhPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + + //rww - adopted id's smoothing code + if ( gameLocal.isClient && !bindMaster && gameLocal.framenum >= smoothedFrame && ( entityNumber != gameLocal.localClientNum || selfSmooth ) ) { + if (!smoothedOriginUpdated) { + idVec3 renderOrigin = TransformToPlayerSpace(modelOffset); + idVec3 originalOrigin = renderOrigin; + + //id's cheesy smooth code (changed to vec3 because there is no "down" in prey) + idVec3 originDiff = renderOrigin - smoothedOrigin; + if (smoothedFrame == 0) { //for teleporting + originDiff = vec3_origin; + } + smoothedOrigin = renderOrigin; + if ( originDiff.LengthSqr() < Square( 100.0f ) ) { + // smoothen by pushing back to the previous position + if ( selfSmooth ) { + assert( entityNumber == gameLocal.localClientNum ); + renderOrigin -= net_clientSelfSmoothing.GetFloat() * originDiff; + } else { + renderOrigin -= gameLocal.clientSmoothing * originDiff; + } + + //get rid of the smoothing on the "vertical" axis + idVec3 a(renderOrigin.x*viewAxis[2].x, renderOrigin.y*viewAxis[2].y, renderOrigin.z*viewAxis[2].z); + idVec3 b(originalOrigin.x*viewAxis[2].x, originalOrigin.y*viewAxis[2].y, originalOrigin.z*viewAxis[2].z); + renderOrigin -= (a-b); + } + + smoothedFrame = gameLocal.framenum; + smoothedOriginUpdated = true; + } + + axis = viewAxis; + origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * GetEyeAxis().Transpose(); + return true; + } + + if (bClampYaw) { //rww - lock model angles + idVec3 masterOrigin; + idMat3 masterAxis; + + if (GetMasterPosition(masterOrigin, masterAxis)) { + axis = masterAxis; + origin = modelOffset; + + return true; + } + } + + axis = viewAxis; + origin = modelOffset; + + return true; +} + +bool hhPlayer::UpdateAnimationControllers( void ) { + bool retValue = idPlayer::UpdateAnimationControllers(); + + hhAnimator *theAnimator; + if (head.IsValid()) { + theAnimator = head->GetAnimator(); + } + else { + theAnimator = GetAnimator(); + } + JawFlap(theAnimator); + + return retValue; +} + +/* +=============== +hhPlayer::Event_PlayWeaponAnim +=============== +*/ +void hhPlayer::Event_PlayWeaponAnim( const char* animName, int numTries ) { + //AOB: I would like a better solution then constantly banging until weapon is valid. + if( (!weapon.IsValid() || GetCurrentWeapon() != idealWeapon) && numTries > 0 ) { + CancelEvents( &EV_PlayWeaponAnim ); + PostEventMS( &EV_PlayWeaponAnim, 50, animName, numTries - 1 ); + return; + } + + CancelEvents( &EV_PlayWeaponAnim ); + if( weapon.IsValid() ) { + weapon->ProcessEvent( &EV_PlayAnimWhenReady, animName ); + } +} + +void hhPlayer::DialogStart(bool bDisallowPlayerDeath, bool bVoiceDucking, bool bLowerWeapon) { + bDialogDamageMode = bDisallowPlayerDeath; + bDialogWeaponMode = bLowerWeapon; + gameLocal.DialogSoundMode(bVoiceDucking); + if (bDialogWeaponMode) { // Lock weapon + preCinematicWeaponFlags = weaponFlags; + preCinematicWeapon = GetIdealWeapon(); + LockWeapon(-1); + } +} + +void hhPlayer::DialogStop() { + bDialogDamageMode = false; + gameLocal.DialogSoundMode(false); + if (bDialogWeaponMode) { + weaponFlags = preCinematicWeaponFlags; + SelectWeapon(preCinematicWeapon, true); + bDialogWeaponMode = false; + } +} + +/* +=============== +hhPlayer::Event_DialogStart + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_DialogStart( int bDisallowPlayerDeath, int bVoiceDucking, int bLowerWeapon ) { + DialogStart(bDisallowPlayerDeath != 0, bVoiceDucking != 0, bLowerWeapon != 0); +} + +/* +=============== +hhPlayer::Event_DialogStop + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_DialogStop() { + DialogStop(); +} + +/* +=============== +hhPlayer::Event_LotaTunnelMode + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_LotaTunnelMode(bool on) { + bLotaTunnelMode = on; +} + +/* +=============== +hhPlayer::Event_Cinematic + HUMANHEAD pdm +=============== +*/ +void hhPlayer::Event_Cinematic( int on, int lockView ) { + bool cinematic = (on != 0); + InCinematic( cinematic ); + playerView.SetLetterBox(cinematic); + if (cinematic) { + if ( IsSpiritOrDeathwalking() ) { + StopSpiritWalk(); + } + if ( IsPossessed() ) { + Unpossess(); + } + + // Lock weapon + preCinematicWeaponFlags = weaponFlags; + preCinematicWeapon = GetCurrentWeapon(); + LockWeapon(-1); + + // disable damage + fl.takedamage = false; + this->lockView = (lockView != 0); + } + else { + fl.takedamage = true; + this->lockView = false; + + //UnlockWeapon(preCinematicWeapon); + weaponFlags = preCinematicWeaponFlags; + //SetCurrentWeapon(preCinematicWeapon); + SelectWeapon(preCinematicWeapon, true); + } +} + +//============================================================================= +// +// hhPlayer::Event_PrepareToResurrect +// +// Flashes the screen / fades the FOV +//============================================================================= + +void hhPlayer::Event_PrepareToResurrect() { + deathWalkFlash = 0; + possessionFOV = 90.0f; + PostEventSec( &EV_ResurrectScreenFade, 0 ); +} + +//============================================================================= +// +// hhPlayer::Event_ResurrectScreenFade +// +// The actual code to incrementally flash the screen +//============================================================================= + +void hhPlayer::Event_ResurrectScreenFade() { + deathWalkFlash += spawnArgs.GetFloat( "resurrectFlashChange", "0.04" ); + if ( deathWalkFlash >= 1.0f ) { + deathWalkFlash = 1.0f; + PostEventSec( &EV_Resurrect, 0.0f ); // Actually send player back to the physical realm + } else { + PostEventSec( &EV_ResurrectScreenFade, 0.02f ); + } + + possessionFOV += spawnArgs.GetFloat( "resurrectFOVChange", "1.0" ); + playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) ); +} + +//============================================================================= +// +// hhPlayer::Event_Resurrect +// +//============================================================================= +void hhPlayer::Event_Resurrect() { + Resurrect(); +} + +//============================================================================= +// +// hhPlayer::Event_Event_ShouldRemainAlignedToAxial +// +//============================================================================= +void hhPlayer::Event_ShouldRemainAlignedToAxial( bool remainAligned ) { + ShouldRemainAlignedToAxial( remainAligned ); +} + +//============================================================================= +// +// hhPlayer::Event_OrientToGravity +// +//============================================================================= +void hhPlayer::Event_OrientToGravity( bool orient ) { + OrientToGravity( orient ); +} + +//============================================================================= +// +// hhPlayer::Event_ResetGravity +// HUMANHEAD: pdm: Posted when entity is leaving a gravity zone +//============================================================================= +void hhPlayer::Event_ResetGravity() { + if( IsWallWalking() ) { + return; // Don't reset if wallwalking + } + + if (spectating) { //HUMANHEAD rww - don't reset if spectating + return; + } + + idPlayer::Event_ResetGravity(); + + OrientToGravity( true ); // let it reset orientation +} + +//============================================================================= +// +// hhPlayer::Event_SetOverlayMaterial +// +//============================================================================= +void hhPlayer::Event_SetOverlayMaterial( const char *mtrName, const int requiresScratch ) { + if ( mtrName && mtrName[0] ) { + playerView.SetViewOverlayMaterial( declManager->FindMaterial(mtrName), requiresScratch ); + } else { + playerView.SetViewOverlayMaterial( NULL ); + } +} + +//============================================================================= +// +// hhPlayer::Event_SetOverlayTime +// +//============================================================================= +void hhPlayer::Event_SetOverlayTime( const float newTime, const int requiresScratch ) { + playerView.SetViewOverlayTime( newTime, requiresScratch ); +} + +//============================================================================= +// +// hhPlayer::Event_SetOverlayColor +// +//============================================================================= +void hhPlayer::Event_SetOverlayColor( const float r, const float g, const float b, const float a ) { + idVec4 color; + + color.x = r; + color.y = g; + color.z = b; + color.w = a; + + playerView.SetViewOverlayColor( color ); +} + +//============================================================================= +// +// hhPlayer::Event_DDAHeartBeat +// +//============================================================================= + +void hhPlayer::Event_DDAHeartBeat() { + gameLocal.GetDDA()->DDA_Heartbeat( this ); + PostEventMS( &EV_DDAHeartbeat, ddaHeartbeatMS ); +} + +//================ +//hhPlayer::Save +//================ +void hhPlayer::Save( idSaveGame *savefile ) const { + // Public vars + savefile->Write(weaponInfo, sizeof(weaponInfo_t)*15); + savefile->Write(altWeaponInfo, sizeof(weaponInfo_t)*15); + savefile->WriteFloat( lighterTemperature ); + savefile->WriteRenderLight( lighter ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( lighterHandle ); + spiritProxy.Save( savefile ); + savefile->WriteInt( lastWeaponSpirit ); + talon.Save( savefile ); + savefile->WriteInt( nextTalonAttackCommentTime ); + savefile->WriteBool( bTalonAttackComment ); + savefile->WriteBool( bSpiritWalk ); + savefile->WriteBool( bDeathWalk ); + savefile->WriteBool( bReallyDead ); + guiWantsControls.Save( savefile ); + savefile->WriteFloat( deathWalkFlash ); + savefile->WriteInt( deathWalkTime ); + savefile->WriteInt( deathWalkPower ); + savefile->WriteBool( bInDeathwalkTransition ); + savefile->WriteBool( bInCinematic ); + savefile->WriteBool( bPlayingLowHealthSound ); + savefile->WriteBool( bPossessed ); + savefile->WriteFloat( possessionTimer ); + savefile->WriteFloat( possessionFOV ); + savefile->WriteInt( preCinematicWeapon ); + savefile->WriteInt( preCinematicWeaponFlags ); + savefile->WriteInt( lastDamagedTime ); + savefile->WriteStaticObject( weaponHandState ); + hand.Save( savefile ); + handNext.Save( savefile ); + possessedTommy.Save( savefile ); + + savefile->WriteStaticObject( vehicleInterfaceLocal ); + savefile->WriteObject( vehicleInterfaceLocal.GetVehicle() ); + + deathLookAtEntity.Save( savefile ); + savefile->WriteString( deathLookAtBone ); + savefile->WriteString( deathCameraBone ); + savefile->WriteStaticObject( spiritwalkSoundController ); + savefile->WriteStaticObject( deathwalkSoundController ); + savefile->WriteStaticObject( wallwalkSoundController ); + savefile->WriteBool( bShowProgressBar ); + savefile->WriteFloat( progressBarValue ); + + savefile->WriteFloat( progressBarGuiValue.GetStartTime() ); // idInterpolate + savefile->WriteFloat( progressBarGuiValue.GetDuration() ); + savefile->WriteFloat( progressBarGuiValue.GetStartValue() ); + savefile->WriteFloat( progressBarGuiValue.GetEndValue() ); + + savefile->WriteInt( progressBarState ); + savefile->WriteBool( bClampYaw ); + savefile->WriteFloat( maxRelativeYaw ); + savefile->WriteFloat( maxRelativePitch ); + savefile->WriteFloat( bob ); + savefile->WriteInt( lastAppliedBobCycle ); + savefile->WriteInt( prevStepUpTime ); + savefile->WriteVec3( prevStepUpOrigin ); + savefile->WriteFloat( crashlandSpeed_fatal ); + savefile->WriteFloat( crashlandSpeed_soft ); + savefile->WriteFloat( crashlandSpeed_jump ); + // Protected vars + savefile->WriteUserInterface( guiOverlay, false ); + thirdPersonCameraClipBounds.Save( savefile ); + savefile->WriteFloat( viewAnglesSensitivity ); + savefile->WriteInt( lastResurrectTime ); + savefile->WriteInt( spiritDrainHeartbeatMS ); + savefile->WriteInt( ddaHeartbeatMS ); + + savefile->WriteInt( spiritWalkToggleTime ); + savefile->WriteBool( bDeathWalkStage2 ); + savefile->WriteBool( bFrozen ); + savefile->WriteAngles( untransformedViewAngles ); + savefile->WriteMat3( untransformedViewAxis ); + savefile->WriteInt( nextSpiritTime ); + + savefile->WriteFloat( cinematicFOV.GetStartTime() ); + savefile->WriteFloat( cinematicFOV.GetAcceleration() ); + savefile->WriteFloat( cinematicFOV.GetDeceleration() ); + savefile->WriteFloat( cinematicFOV.GetDuration() ); + savefile->WriteFloat( cinematicFOV.GetStartValue() ); + savefile->WriteFloat( cinematicFOV.GetEndValue() ); + savefile->WriteBool( bAllowSpirit ); + savefile->WriteInt( airAttackerTime ); + savefile->WriteBool( bScopeView ); + savefile->WriteInt( ddaNumEnemies ); + savefile->WriteFloat( ddaProbabilityAccum ); + savefile->WriteInt( weaponFlags ); + savefile->WriteBool( lockView ); + + savefile->WriteBool( bCollidingWithPortal ); + savefile->WriteBool( bLotaTunnelMode ); + savefile->WriteInt( forcePredictionButtons ); + + for (int ix=0; ixWriteInt( lastAttackers[ix].time ); + savefile->WriteBool( lastAttackers[ix].displayed ); + } + + if ( InVehicle() ) { + savefile->WriteMat3( vehicleInterfaceLocal.GetVehicle()->GetPhysics()->GetAxis() ); + savefile->WriteVec3( vehicleInterfaceLocal.GetVehicle()->GetAxis()[0] ); + } + + //HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem + cameraInterpolator.Save( savefile ); + //HUMANHEAD PCF mdl 05/04/06 - Save whether the light handle is active + savefile->WriteBool( IsLighterOn() ); +} + +//================ +//hhPlayer::Restore +//================ +void hhPlayer::Restore( idRestoreGame *savefile ) { + // Public vars + savefile->Read(weaponInfo, sizeof(weaponInfo_t)*15); + savefile->Read(altWeaponInfo, sizeof(weaponInfo_t)*15); + savefile->ReadFloat( lighterTemperature ); + savefile->ReadRenderLight( lighter ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( lighterHandle ); + spiritProxy.Restore( savefile ); + savefile->ReadInt( lastWeaponSpirit ); + talon.Restore( savefile ); + savefile->ReadInt( nextTalonAttackCommentTime ); + savefile->ReadBool( bTalonAttackComment ); + savefile->ReadBool( bSpiritWalk ); + savefile->ReadBool( bDeathWalk ); + savefile->ReadBool( bReallyDead ); + guiWantsControls.Restore( savefile ); + savefile->ReadFloat( deathWalkFlash ); + savefile->ReadInt( deathWalkTime ); + savefile->ReadInt( deathWalkPower ); + savefile->ReadBool( bInDeathwalkTransition ); + savefile->ReadBool( bInCinematic ); + savefile->ReadBool( bPlayingLowHealthSound ); + savefile->ReadBool( bPossessed ); + savefile->ReadFloat( possessionTimer ); + savefile->ReadFloat( possessionFOV ); + savefile->ReadInt( preCinematicWeapon ); + savefile->ReadInt( preCinematicWeaponFlags ); + savefile->ReadInt( lastDamagedTime ); + savefile->ReadStaticObject( weaponHandState ); + hand.Restore( savefile ); + handNext.Restore( savefile ); + possessedTommy.Restore( savefile ); + + savefile->ReadStaticObject( vehicleInterfaceLocal ); + SetVehicleInterface( &vehicleInterfaceLocal ); + + hhVehicle *vehicle; + savefile->ReadObject( reinterpret_cast ( vehicle ) ); + if( vehicle ) { + vehicle->RestorePilot( &vehicleInterfaceLocal ); + } + + deathLookAtEntity.Restore( savefile ); + savefile->ReadString( deathLookAtBone ); + savefile->ReadString( deathCameraBone ); + savefile->ReadStaticObject( spiritwalkSoundController ); + savefile->ReadStaticObject( deathwalkSoundController ); + savefile->ReadStaticObject( wallwalkSoundController ); + savefile->ReadBool( bShowProgressBar ); + savefile->ReadFloat( progressBarValue ); + + float set; + savefile->ReadFloat( set ); // idInterpolate + progressBarGuiValue.SetStartTime( set ); + savefile->ReadFloat( set ); + progressBarGuiValue.SetDuration( set ); + savefile->ReadFloat( set ); + progressBarGuiValue.SetStartValue(set); + savefile->ReadFloat( set ); + progressBarGuiValue.SetEndValue( set ); + + savefile->ReadInt( progressBarState ); + savefile->ReadBool( bClampYaw ); + savefile->ReadFloat( maxRelativeYaw ); + savefile->ReadFloat( maxRelativePitch ); + savefile->ReadFloat( bob ); + savefile->ReadInt( lastAppliedBobCycle ); + savefile->ReadInt( prevStepUpTime ); + savefile->ReadVec3( prevStepUpOrigin ); + savefile->ReadFloat( crashlandSpeed_fatal ); + savefile->ReadFloat( crashlandSpeed_soft ); + savefile->ReadFloat( crashlandSpeed_jump ); + // Protected vars + savefile->ReadUserInterface( guiOverlay ); + + thirdPersonCameraClipBounds.Restore( savefile ); + savefile->ReadFloat( viewAnglesSensitivity ); + savefile->ReadInt( lastResurrectTime ); + savefile->ReadInt( spiritDrainHeartbeatMS ); + savefile->ReadInt( ddaHeartbeatMS ); + + savefile->ReadInt( spiritWalkToggleTime ); + savefile->ReadBool( bDeathWalkStage2 ); + savefile->ReadBool( bFrozen ); + savefile->ReadAngles( untransformedViewAngles ); + savefile->ReadMat3( untransformedViewAxis ); + savefile->ReadInt( nextSpiritTime ); + + float startTime, accelTime, decelTime, duration, startPos, endPos; + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( startPos ); + savefile->ReadFloat( endPos ); + cinematicFOV.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + savefile->ReadBool( bAllowSpirit ); + savefile->ReadInt( airAttackerTime ); + savefile->ReadBool( bScopeView ); + savefile->ReadInt( ddaNumEnemies ); + savefile->ReadFloat( ddaProbabilityAccum ); + savefile->ReadInt( weaponFlags ); + savefile->ReadBool( lockView ); + + savefile->ReadBool( bCollidingWithPortal ); + savefile->ReadBool( bLotaTunnelMode ); + savefile->ReadInt( forcePredictionButtons ); + + for (int ix=0; ixReadInt( lastAttackers[ix].time ); + savefile->ReadBool( lastAttackers[ix].displayed ); + } + + // We don't want to preserve these + memset( &oldCmdAngles, 0, sizeof( oldCmdAngles ) ); + + kickSpring = spawnArgs.GetFloat( "kickSpring" ); //HUMANHEAD bjk + kickDamping = spawnArgs.GetFloat( "kickDamping" ); //HUMANHEAD bjk + + if( InVehicle() ) { + hhVehicle *vehicle = vehicleInterfaceLocal.GetVehicle(); + HH_ASSERT( vehicle ); + + idMat3 axis; + idVec3 axis0; + + // This prevents a crashbug if the vehicle's physics doesn't exist yet. + savefile->ReadMat3( axis ); // vehicle->GetPhysics()->GetAxis() + savefile->ReadVec3( axis0 ); // vehicle->GetAxis()[0] + + // Keep orientation correct in vehicles + RestoreOrientation( GetOrigin(), axis, axis0, axis0.ToAngles() ); + + //HUMANHEAD PCF mdl 05/02/06 - Added this to re-enable shuttle view + if (renderSystem) { + // Set shuttle view + renderSystem->SetShuttleView( true ); + } + } else { + // Keep orientation correct for walkwalk and gravity rooms + RestoreOrientation( GetOrigin(), physicsObj.GetAxis(), viewAngles.ToMat3()[0], untransformedViewAngles ); + } + + //HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem + cameraInterpolator.Restore( savefile ); + + //HUMANHEAD PCF mdl 05/04/06 - Restore lighter + bool bLighter; + savefile->ReadBool( bLighter ); + if ( bLighter ) { + lighterHandle = gameRenderWorld->AddLightDef( &lighter ); + } +} + +int hhPlayer::GetSpiritPower() { + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower"); + return inventory.ammo[ammo_spiritpower]; +} + +void hhPlayer::SetSpiritPower(int amount) { + ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower"); + inventory.ammo[ammo_spiritpower] = amount; + if (inventory.ammo[ammo_spiritpower] > inventory.maxSpirit) { + inventory.ammo[ammo_spiritpower] = inventory.maxSpirit; + } +} + +void hhPlayer::Event_StartHUDTranslation() { + vehicleInterfaceLocal.StartHUDTranslation(); +} + +void hhPlayer::Freeze(float unfreezeDelay) { + bFrozen = true; + StopFiring(); + if ( weapon.IsValid() ) { + weapon->PutAway(); + } + if ( unfreezeDelay > 0.0f ) { + PostEventSec( &EV_Unfreeze, unfreezeDelay ); + } +} + +void hhPlayer::Unfreeze(void) { + bFrozen = false; + if ( weapon.IsValid() ) { + weapon->PostEventMS( &EV_Show, 1000 ); + weapon->PostEventMS( &EV_Weapon_WeaponRising, 1000 ); + } +} + +void hhPlayer::Event_Unfreeze() { + Unfreeze(); + if ( bindMaster && bindMaster->RespondsTo( EV_BindUnfroze ) ) { + GetBindMaster()->PostEventMS( &EV_BindUnfroze, 0, this ); + } +} + +void hhPlayer::Event_GetSpiritPower() { //rww + idThread::ReturnFloat((float)GetSpiritPower()); +} + +void hhPlayer::Event_SetSpiritPower(const float s) { //rww + SetSpiritPower((int)s); +} + +void hhPlayer::Event_OnGround() { // bg + idThread::ReturnFloat( physicsObj.HasGroundContacts() ); +} + +void hhPlayer::SetupWeaponFlags( void ) { + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDecl *mapDecl = declManager->FindType(DECL_MAPDEF, mapName, false ); + if ( !mapDecl ) { + weaponFlags = -1; // No map decls? Allow all weapons + return; + } + const idDeclEntityDef *mapInfo = static_cast(mapDecl); + + const char *weaponList = mapInfo->dict.GetString( "disableWeapons" ); + weaponFlags = 0; + if ( weaponList && weaponList[0] ) { + if ( !idStr::Icmp( weaponList, "all" ) ) { + // No weapons are enabled, so just return + return; + } + + if ( idStr::FindText( weaponList, "wrench", false ) == -1 ) { + weaponFlags |= HH_WEAPON_WRENCH; + } + if ( idStr::FindText( weaponList, "rifle", false ) == -1 ) { + weaponFlags |= HH_WEAPON_RIFLE; + } + if ( idStr::FindText( weaponList, "crawler", false ) == -1 ) { + weaponFlags |= HH_WEAPON_CRAWLER; + } + if ( idStr::FindText( weaponList, "autocannon", false ) == -1 ) { + weaponFlags |= HH_WEAPON_AUTOCANNON; + } + if ( idStr::FindText( weaponList, "hiderweapon", false ) == -1 ) { + weaponFlags |= HH_WEAPON_HIDERWEAPON; + } + if ( idStr::FindText( weaponList, "rocketlauncher", false ) == -1 ) { + weaponFlags |= HH_WEAPON_ROCKETLAUNCHER; + } + if ( idStr::FindText( weaponList, "soulstripper", false ) == -1 ){ + weaponFlags |= HH_WEAPON_SOULSTRIPPER; + } + } + + if ( weaponFlags == 0 ) { + // Allow all weapons by default + weaponFlags = -1; + } +} + +void hhPlayer::LockWeapon( int weaponNum ) { + // Special case, -1 locks all weapons AND the lighter + if ( weaponNum == -1 ) { + if (IsLighterOn()) { + LighterOff(); + } + if (weapon.IsValid()) { + weapon->PutAway(); + } + weaponFlags = 0; + idealWeapon = 0; + currentWeapon = 0; + return; + } + + if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) { + gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum ); + } + + int flag = ( 1 << ( weaponNum - 1 ) ); + if ( weaponFlags & flag ) { // Only lock if not already locked + weaponFlags &= ~flag; + if ( idealWeapon == weaponNum ) { + if (weapon.IsValid()) { + weapon->PutAway(); + } + NextWeapon(); + } + } +} + +void hhPlayer::UnlockWeapon( int weaponNum ) { + // Special case, -1 unlocks all weapons + if ( weaponNum == -1 ) { + weaponFlags = -1; + if ( idealWeapon == 0 ) { + idealWeapon = 1; // Default to the wrench + } + return; + } + + if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) { + gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum ); + } + + int flag = ( 1 << ( weaponNum - 1 ) ); + if ( weaponFlags & flag ) { + // Already unlocked + return; + } + + weaponFlags |= flag; + idealWeapon = weaponNum; +} + +bool hhPlayer::IsLocked(int weaponNum) { + //HUMANHEAD PCF mdl 05/05/06 - Changed to < 1 to catch weaponNum = -1 + if (weaponNum < 1) { + return true; // CJR: if the player has no weapons, then consider all weapons locked + } + return !(weaponFlags & (1 << (weaponNum-1))); +} + +void hhPlayer::Event_LockWeapon( int weaponNum ) { + LockWeapon( weaponNum ); +} + +void hhPlayer::Event_UnlockWeapon( int weaponNum ) { + UnlockWeapon( weaponNum ); +} + +//HUMANHEAD rdr +void hhPlayer::Event_SetPrivateCameraView( idEntity *camView, int noHide ) { + if ( camView && camView->IsType( idCamera::Type ) ) { + idPlayer::SetPrivateCameraView( static_cast( camView ), noHide != 0 ); + } + else { + idPlayer::SetPrivateCameraView( NULL ); + } +} + +//HUMANHEAD rdr +void hhPlayer::Event_SetCinematicFOV( float fieldOfView, float accelTime, float decelTime, float duration ) { + if ( duration > 0.f ) { + cinematicFOV.Init( gameLocal.time, SEC2MS( accelTime ), SEC2MS( decelTime ), SEC2MS( duration ), cinematicFOV.GetCurrentValue( gameLocal.GetTime() ), fieldOfView ); + } else { + cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, cinematicFOV.GetEndValue(), fieldOfView ); + } +} + +//HUMANHEAD rww +void hhPlayer::Event_StopSpiritWalk() { + StopSpiritWalk(); +} + +void hhPlayer::Event_DamagePlayer(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, char *damageDefName, float damageScale, int location) { + Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} +//HUMANHEAD END + +void hhPlayer::Event_GetSpiritProxy() { + if ( spiritProxy.IsValid() ) { + idThread::ReturnEntity( spiritProxy.GetEntity() ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +void hhPlayer::Event_IsSpiritWalking() { + idThread::ReturnInt( IsSpiritWalking() ? 1 : 0 ); +} + +void hhPlayer::Event_IsDeathWalking() { + idThread::ReturnInt( IsDeathWalking() ? 1 : 0 ); +} + +void hhPlayer::Event_AllowLighter( bool allow ) { + inventory.requirements.bCanUseLighter = allow; + if ( !allow && IsLighterOn() ) { + LighterOff(); + } +} + +//============================================================================= +// +// hhPlayer::Event_GetDDAValue +// +// Returns the current DDA value from zero to one +//============================================================================= + +void hhPlayer::Event_GetDDAValue() { + idThread::ReturnFloat( gameLocal.GetDDAValue() ); +} + +//HUMANHEAD rww +hhArtificialPlayer::hhArtificialPlayer(void) { + memset(&lastAICmd, 0, sizeof(lastAICmd)); + testCrouchActive = false; + testCrouchTime = 0; +} + +void hhArtificialPlayer::Spawn( void ) { + idDict apUI; + gameLocal.GetAPUserInfo(apUI, entityNumber); + gameLocal.SetUserInfo(entityNumber, apUI, gameLocal.isClient, false); + + SetPlayerModel(false); //force an update on the player model +} + +void hhArtificialPlayer::Think( void ) { + PROFILE_START("hhArtificialPlayer::Think", PROFMASK_NORMAL); + bool iFeelHoppy = false; + usercmd_t *nextCmd = &gameLocal.usercmds[ entityNumber ]; + idAngles idealAngles = GetViewAngles(); + + nextCmd->gameFrame = gameLocal.framenum; + nextCmd->gameTime = gameLocal.time; + + nextCmd->forwardmove = idMath::ClampChar(127); + + //this is not meant to resemble proper ai logic, it's just for testing. + idEntity *closestEnt = NULL; + float closestDist = idMath::INFINITY; + + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + numSourceAreas = gameRenderWorld->BoundsInAreas( GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle_t pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + + for(int i=0;iIsType(hhPlayer::Type)); + hhPlayer *plEnt = static_cast(ent); + + float l = (ent->GetOrigin()-GetOrigin()).Length(); + if(l < 4096.0f && (l < closestDist || !closestEnt) && ent->PhysicsTeamInPVS(pvsHandle)) { + trace_t tr; + + gameLocal.clip.TracePoint(tr, GetOrigin(), ent->GetOrigin(), GetPhysics()->GetClipMask(), this); + if (tr.c.entityNum == ent->entityNumber) { //if we hit the thing then it's visible. + if (ent->health > 0) { + if (gameLocal.gameType != GAME_TDM || plEnt->team != team) { + closestDist = l; + closestEnt = ent; + } + } + } + } + } + } + + gameLocal.pvs.FreeCurrentPVS( pvsHandle ); + + if (closestEnt) { + idealAngles = (closestEnt->GetOrigin()-GetOrigin()).ToAngles(); + } + else { //if no one to run mindlessly at, do some stuff. + const float testDist = 64.0f; + idVec3 fwd = GetOrigin()+(idealAngles.ToForward()*testDist); + + trace_t tr; + gameLocal.clip.TraceBounds(tr, GetOrigin(), fwd, GetPhysics()->GetBounds(), GetPhysics()->GetClipMask(), this); + if (tr.fraction != 1.0f) { //if we hit something set the yaw to a new random angle. + iFeelHoppy = true; + + idealAngles.yaw = rand()%360; + } + } + + if ((closestEnt || health <= 0) && rand()%2 == 1) { + nextCmd->buttons |= BUTTON_ATTACK; + } + else { + nextCmd->buttons &= ~BUTTON_ATTACK; + } + + if (testCrouchTime < gameLocal.time) { + testCrouchTime = gameLocal.time + rand()%4000; + testCrouchActive = !testCrouchActive; + } + + if (iFeelHoppy) { + nextCmd->upmove = idMath::ClampChar(127); + } + else { + if (testCrouchActive) { + nextCmd->upmove = idMath::ClampChar(-127); + } + else { + nextCmd->upmove = 0; + } + } + + idealAngles.pitch = idMath::AngleNormalize180(idealAngles.pitch); + idealAngles.yaw = idMath::AngleNormalize180(idealAngles.yaw); + idealAngles.roll = idMath::AngleNormalize180(idealAngles.roll); + SetUntransformedViewAngles(idAngles(0.0f, 0.0f, 0.0f)); + UpdateDeltaViewAngles(idealAngles); + UpdateOrientation(idealAngles); + PROFILE_STOP("hhArtificialPlayer::Think", PROFMASK_NORMAL); + hhPlayer::Think(); +} + +void hhArtificialPlayer::ClientPredictionThink( void ) { + gameLocal.usercmds[entityNumber] = lastAICmd; //copy over the last manually snapshotted usercmd first + hhPlayer::ClientPredictionThink(); +} + +void hhArtificialPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + //instead of sync'ing, just mirror what the server has in ::Spawn + /* + msg.WriteDict(*gameLocal.GetUserInfo(entityNumber)); //ap's don't broadcast their info to the server, so the server otherwise + //will never broadcast it out to other clients + */ + + hhPlayer::WriteToSnapshot(msg); + + usercmd_t &cmd = gameLocal.usercmds[entityNumber]; + 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] ); + + msg.WriteBits(spectating, 1); +} + +void hhArtificialPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + /* + ((idBitMsgDelta)msg).ReadDict(*((idDict *)gameLocal.GetUserInfo(entityNumber))); + if (!clientReceivedUI) { + UserInfoChanged( false ); + clientReceivedUI = true; + } + */ + + hhPlayer::ReadFromSnapshot(msg); + + usercmd_t &cmd = lastAICmd; + 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(); + + bool spec = !!msg.ReadBits(1); //doesn't go through right for ap's or something based on events sometimes, hack fix + if (spec != spectating) { + Spectate(spec); + } +} +//HUMANHEAD END + +void hhPlayer::DisableSpiritWalk(int timeout) { + nextSpiritTime = gameLocal.time + SEC2MS(timeout); + StopSpiritWalk(); +} + +//============================================================================= +// +// hhPlayer::Event_UpdateDDA +// +// CJR: Update the probability the player will die this tick, and +// adjust the difficulty accordingly +//============================================================================= + +void hhPlayer::Event_UpdateDDA() { + idEntity *ent; + int updateFlags; + + // Find the number of alive, non-dormant creatures attacking the player + ddaNumEnemies = 0; + ddaProbabilityAccum = 1.0f; + updateFlags = 0; + + for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent->fl.isDormant || !ent->IsType( hhMonsterAI::Type ) || ent->fl.hidden || ent->health <= 0 ) { + continue; + } + + hhMonsterAI *monster = static_cast(ent); + if ( monster->GetEnemy() != this ) { + continue; + } + + // PVS Check: only include creatures that the player can attack or be attacked by + if ( !gameLocal.InPlayerPVS( ent ) ) { + continue; + } + + // Accumulate the survival rate against each enemy + int index = monster->spawnArgs.GetInt( "ddaIndex", "0" ); + + if ( index == -1 ) { // Skip certain monsters, such as the vacuum or the crawlers + continue; + } + + float prob = gameLocal.GetDDA()->DDA_GetProbability( index, GetHealth() ); + + ddaProbabilityAccum *= prob; + + // Recompute the actual difficulty based upon the creatures in view + updateFlags |= ( 1 << index ); + + ddaNumEnemies++; + } + + gameLocal.GetDDA()->RecalculateDifficulty( updateFlags ); + + PostEventSec( &EV_UpdateDDA, spawnArgs.GetFloat( "updateDDARate", "0.25" ) ); + + if ( g_printDDA.GetBool() ) { + if ( ddaNumEnemies > 0 ){ + common->Printf("Probability [%.2f]\n", ddaProbabilityAccum ); + } + } +} + +void hhPlayer::Event_DisableSpirit(void) { + // HUMANHEAD PCF pdm 05-17-06: Removed assert, as scripters were potentially calling during deathwalk + // HUMANHEAD PCF pdm 05-17-06: Only exit spirit realm if spiritwalking, not deathwalking + + if ( IsSpiritWalking() ) { + StopSpiritWalk(); + } + + bAllowSpirit = false; +} + +void hhPlayer::Event_EnableSpirit(void) { + // HUMANHEAD PCF pdm 05-17-06: Removed assert + + bAllowSpirit = true; +} + +//HUMANHEAD bjk +void hhPlayer::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + // no need for these in sp + if( gameLocal.isMultiplayer ) { + idActor::ProjectOverlay( origin, dir, size, material ); + } +} + +// HUMANHEAD mdl: Ripped from idAI for short invulnerability after returning from deathwalk +void hhPlayer::Event_AllowDamage( void ) { + fl.takedamage = true; +} + +void hhPlayer::Event_IgnoreDamage( void ) { + fl.takedamage = false; +} + +// Precompute all the weapons' info for faster HUD updates +void hhPlayer::SetupWeaponInfo() { + const char *fireInfoName = NULL; + const idDeclEntityDef *decl = NULL; + const char *ammoName = NULL; + float maxAmmo; + + memset(weaponInfo, 0, sizeof(weaponInfo_t)*15); + memset(altWeaponInfo, 0, sizeof(weaponInfo_t)*15); + + for (int ix=0; ix<15; ix++) { + const char *weaponClassName = spawnArgs.GetString( va( "def_weapon%d", ix ), NULL ); + if ( weaponClassName && *weaponClassName ) { + + const idDeclEntityDef *weaponObjDecl = gameLocal.FindEntityDef( weaponClassName, false ); + if ( weaponObjDecl ) { + fireInfoName = weaponObjDecl->dict.GetString("def_fireInfo"); + decl = gameLocal.FindEntityDef( fireInfoName, false ); + if ( decl ) { + weaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); + weaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired"); + weaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo"); + + ammoName = idWeapon::GetAmmoNameForNum( weaponInfo[ix].ammoType ); + maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName ); + weaponInfo[ix].ammoMax = max(1, maxAmmo); + } + + fireInfoName = weaponObjDecl->dict.GetString("def_altFireInfo"); + decl = gameLocal.FindEntityDef( fireInfoName, false ); + if ( decl ) { + altWeaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); + altWeaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired"); + altWeaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo"); + + ammoName = idWeapon::GetAmmoNameForNum( altWeaponInfo[ix].ammoType ); + maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName ); + altWeaponInfo[ix].ammoMax = max(1, maxAmmo); + } + } + } + } +} + +/* +=============== +hhPlayer::ThrowGrenade +HUMANHEAD bjk +=============== +*/ +void hhPlayer::ThrowGrenade( void ) { + idMat3 axis; + idVec3 muzzle; + + if( inventory.HasAmmo(inventory.AmmoIndexForAmmoClass( "ammo_crawler" ), 1) == false ) + return; + + if (IsSpiritOrDeathwalking() || InVehicle() || bReallyDead) { + return; + } + + if ( privateCameraView || !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 || gameLocal.isClient ) + return; + + if( weapon.IsValid() ) { + if ( inventory.weapons & ( 1 << 12 ) && currentWeapon != 12 && currentWeapon != 3 && idealWeapon == currentWeapon ) { + idealWeapon = 12; + previousWeapon = currentWeapon; + } + else if( currentWeapon == 3 ) { + FireWeapon(); + usercmd.buttons = usercmd.buttons | BUTTON_ATTACK; + } + } +} + +//HUMANHEAD bjk +void hhPlayer::Event_ReturnToWeapon() { + const char *weap; + + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + idThread::ReturnInt(0); + return; + } + + if ( ( previousWeapon < 0 ) || ( previousWeapon >= MAX_WEAPONS ) ) { + idThread::ReturnInt(0); + return; + } + + if ( gameLocal.isClient ) { + idThread::ReturnInt(0); + return; + } + + weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); + if ( !weap[ 0 ] ) { + gameLocal.Printf( "Invalid weapon\n" ); + idThread::ReturnInt(0); + return; + } + + if ( inventory.weapons & ( 1 << previousWeapon ) ) { + idealWeapon = previousWeapon; + } + + idThread::ReturnInt(1); +} + +//HUMANHEAD rww +void hhPlayer::Event_CanAnimateTorso(void) { + if (!gameLocal.isMultiplayer) { //don't worry about it + idThread::ReturnInt(1); + return; + } + + if (!weapon.IsValid() || !weapon->IsType(hhWeapon::Type)) { //bad + idThread::ReturnInt(0); + return; + } + + idThread::ReturnInt(1); +} + +void hhPlayer::Show(void) { + idActor::Show(); + + hhWeapon *weap; + weap = weapon.GetEntity(); + if ( weap ) { + if (!IsLocked(currentWeapon)) { + weap->ShowWorldModel(); + } else { + weap->HideWorldModel(); + } + } +} diff --git a/neo/prey/Prey/game_player.h b/neo/prey/Prey/game_player.h new file mode 100644 index 00000000..5a743cc5 --- /dev/null +++ b/neo/prey/Prey/game_player.h @@ -0,0 +1,771 @@ + +#ifndef __PREY_GAME_PLAYER_H__ +#define __PREY_GAME_PLAYER_H__ + +extern const idEventDef EV_PlayWeaponAnim; +extern const idEventDef EV_Resurrect; +extern const idEventDef EV_PrepareToResurrect; +extern const idEventDef EV_SetOverlayMaterial; +extern const idEventDef EV_SetOverlayTime; +extern const idEventDef EV_SetOverlayColor; +extern const idEventDef EV_ShouldRemainAlignedToAxial; +extern const idEventDef EV_StartHudTranslation; +extern const idEventDef EV_StopSpiritWalk; //rww +extern const idEventDef EV_DamagePlayer; //rww + +// HUMANHEAD IMPULSES SHOULD START AT 50 +const int IMPULSE_50 = 50; // Unused +const int IMPULSE_51 = 51; // Unused +const int IMPULSE_52 = 52; // Unused +const int IMPULSE_53 = 53; // Unused +const int IMPULSE_54 = 54; // Spirit power toggle + +//Declared in idPlayer.cpp +extern const int RAGDOLL_DEATH_TIME; +extern const int LADDER_RUNG_DISTANCE; +extern const int HEALTH_PER_DOSE; +extern const int WEAPON_DROP_TIME; +extern const int WEAPON_SWITCH_DELAY; +extern const int SPECTATE_RAISE; +extern const int HEALTHPULSE_TIME; +extern const float MIN_BOB_SPEED; + +// Forward declaration +class hhSpiritProxy; +class hhConsole; +class hhTalon; +class hhPossessedTommy; + +#define MAX_HEALTH_NORMAL_MP 100 //rww - a probably temporary hack for trying out the pipe-as-armor concept + +#define HH_WEAPON_WRENCH 1 +#define HH_WEAPON_RIFLE 2 +#define HH_WEAPON_CRAWLER 4 +#define HH_WEAPON_AUTOCANNON 8 +#define HH_WEAPON_HIDERWEAPON 16 +#define HH_WEAPON_ROCKETLAUNCHER 32 +#define HH_WEAPON_SOULSTRIPPER 64 + +// Structure to hold all info about the weapons to remove the dictionary lookups every tick +typedef struct weaponInfo_s { + int ammoType; + int ammoLow; + int ammoMax; + int ammoRequired; +} weaponInfo_t; + +#define MAX_TRACKED_ATTACKERS 3 +typedef struct attackInfo_s { + idEntityPtr attacker; + int time; + bool displayed; +} attackInfo_t; + + +class hhPlayer : public idPlayer { + CLASS_PROTOTYPE(hhPlayer); + +public: + weaponInfo_t weaponInfo[15]; + weaponInfo_t altWeaponInfo[15]; + float lighterTemperature; // Temp of the lighter. 0 = cold, 1 = too hot to use + renderLight_t lighter; // lighter + int lighterHandle; + + idEntityPtr spiritProxy; // Proxy player when the player is spiritwalking + int lastWeaponSpirit; // Last weapon the player held before entering spirit mode + idEntityPtr talon; // Talon, the spirit hawk + int nextTalonAttackCommentTime; // Time between when Tommy can make talon comments + bool bTalonAttackComment; // If true, Tommy can randomly make comments when Talon is attacking + bool bSpiritWalk; // True if the player is spirit walking + bool bDeathWalk; // True if the player is dead and DeathWalking + bool bReallyDead; // True if the player truly died (only when in deathwalk mode) + idEntityPtr guiWantsControls; // Gui console that controls should be routed to + idEntityPtr possessedTommy; // ptr to the possessed version of the player + + //rww - keep track of who attacked us in air, so if we die from unnatural causes other than a player, + //the person who last attacked will be credited. + idEntityPtr airAttacker; + int airAttackerTime; + + float deathWalkFlash; + int deathWalkTime; // time the player entered deathwalk + int deathWalkPower; // Power in deathwalk - when full, the player wins deathwalk + bool bInDeathwalkTransition; + + + bool bInCinematic; + bool lockView; + + bool bPlayingLowHealthSound; + bool bPossessed; // True if the player can be possessed + float possessionTimer; // Countdown timer until the player is fully possessed + float possessionFOV; // FOV for possession + + int preCinematicWeapon; // selected weapon before cinematic started for restoration + int preCinematicWeaponFlags; // flags to restore after cinematic completes + + int lastDamagedTime; // Used to determine when to recharge health + + void ReportAttack(idEntity *attacker); + attackInfo_t lastAttackers[MAX_TRACKED_ATTACKERS]; // Used for tracking attackers for HUD + + //Make these idEntityPtr just to be safe + hhWeaponHandState weaponHandState; + idEntityPtr hand; + idEntityPtr handNext; // Next hand to pop up + hhPlayerVehicleInterface vehicleInterfaceLocal; + + // Death Variables + idEntityPtr deathLookAtEntity; // nla - Entity to look at when dead + idStr deathLookAtBone; // nla - Entity's bone to look at when dead + idStr deathCameraBone; + + hhCameraInterpolator cameraInterpolator; + idAngles oldCmdAngles; // HUMANHEAD: aob + bool bClampYaw; // HUMANHEAD pdm + float maxRelativeYaw; // HUMANHEAD pdm + float maxRelativePitch; // HUMANHEAD rww + + hhSoundLeadInController spiritwalkSoundController; + hhSoundLeadInController deathwalkSoundController; + hhSoundLeadInController wallwalkSoundController; + + bool bShowProgressBar; // Whether to display progress bar on HUDs + float progressBarValue; // Current progress (set by script) + idInterpolate progressBarGuiValue; // Interpolates towards progressBarValue + int progressBarState; // 0=none, 1=success, 2=failure + + bool bScopeView; // HUMANHEAD rww + + // HUMANHEAD bjk + float kickSpring; + float kickDamping; + // HUMANHEAD END + +#if GAMEPAD_SUPPORT // VENOM BEGIN +//SAVETODO: save these + int lastAutoLevelTime; + int lastAccelTime; + float lastAccelFactor; +#endif // VENOM END + + float bob; + int lastAppliedBobCycle; + int prevStepUpTime; + idVec3 prevStepUpOrigin; + + //Crashland + float crashlandSpeed_fatal; + float crashlandSpeed_soft; + float crashlandSpeed_jump; + + // CJR: DDA variables + int ddaNumEnemies; // CJR + float ddaProbabilityAccum; + + int forcePredictionButtons; //HUMANHEAD rww + + idScriptBool AI_ASIDE; + + // Methods + hhPlayer(); + void Spawn( void ); + virtual ~hhPlayer(); + + + // Overridden Methods + virtual void RestorePersistantInfo( void ); + virtual void SquishedByDoor(idEntity *door); + virtual void Init(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ); + virtual void Think( void ); + virtual void AdjustBodyAngles( void ); + virtual void Move( void ); + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + virtual void Teleport( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles, idEntity *destination ); + virtual void TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis ); // HUMANHEAD cjr: A teleport that doesn't telefrag + virtual void TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles ); + virtual void LinkScriptVariables( void ); + virtual void Present(); + + virtual void UpdateFromPhysics( bool moveBack ); + virtual bool UpdateAnimationControllers( void ); + + virtual bool Give( const char *statname, const char *value ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); //rww - override idPlayer + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); //rww + + virtual void WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ); + //rww - our own prediction function + virtual void ClientPredictionThink( void ); + + bool SetWeaponSpawnId( int id ) { return weapon.SetSpawnId( id ); } + + virtual void NextBestWeapon( void ); + virtual void SelectWeapon( int num, bool force ); + virtual void NextWeapon( void ); + virtual void PrevWeapon( void ); + virtual void UpdateWeapon( void ); + virtual void UpdateFocus( void ); + virtual void UpdateViewAngles( void ); + virtual void UpdateDeltaViewAngles( const idAngles &angles ); + virtual idAngles DetermineViewAngles( const usercmd_t& cmd, idAngles& cmdAngles ); + virtual bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + virtual void GetViewPos( idVec3 &origin, idMat3 &axis ); //HUMANHEAD bjk + + virtual void UpdateHud( idUserInterface *_hud ); + virtual void UpdateHudWeapon(bool flashWeapon = true); + virtual void UpdateHudAmmo(idUserInterface *_hud); + virtual void UpdateHudStats( idUserInterface *_hud ); + virtual void DrawHUD( idUserInterface *_hud ); + virtual void DrawHUDVehicle( idUserInterface* _hud );//HUMANHEAD: aob + virtual void Weapon_Combat( void ); + virtual void Weapon_GUI( void ); + virtual void PerformImpulse(int impulse); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + + virtual int HasAmmo( ammo_t type, int amount ); + virtual bool UseAmmo( ammo_t type, int amount ); + + virtual void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + virtual void BobCycle( const idVec3 &pushVelocity ); + virtual void OffsetThirdPersonView( float angle, float range, float height, bool clip ); + virtual void CalculateRenderView( 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 bool IsWallWalking( void ) const; + virtual bool WasWallWalking( void ) const; + virtual float GetStepHeight() const; + + virtual int GetWeaponNum( const char* weaponName ) const; + virtual const char* GetWeaponName( int num ) const; + + virtual idAngles GunTurningOffset( void ) { return idPlayer::GunTurningOffset(); } + virtual idVec3 GunAcceleratingOffset( void ) { return idPlayer::GunAcceleratingOffset(); } + + virtual void SetupWeaponEntity( void ); + + virtual void SetUntransformedViewAngles( const idAngles& newUntransformedViewAngles ) { untransformedViewAngles = newUntransformedViewAngles; } + virtual void SetUntransformedViewAxis( const idMat3& newUntransformedViewAxis ) { untransformedViewAxis = newUntransformedViewAxis; } + virtual const idAngles& GetUntransformedViewAngles() const; + virtual const idMat3& GetUntransformedViewAxis() const; + virtual idAngles GetViewAngles() const; + virtual void SetEyeAxis( const idMat3 &axis ); + virtual void SetEyeHeight( float height ); + virtual float EyeHeight( void ) const; + virtual idVec3 EyeOffset( void ) const; + virtual idMat3 GetEyeAxis() const; + virtual idAngles GetEyeAxisAsAngles() const; + virtual idQuat GetEyeAxisAsQuat() const; + void ShouldRemainAlignedToAxial( bool remainAligned ); + void OrientToGravity( bool bRotateToGravity ); + void SetOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ); + void RestoreOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ); + void UpdateOrientation( const idAngles& newUntransformedViewAngles ); + virtual bool CheckFOV( const idVec3 &pos ); + virtual bool CheckYawFOV( const idVec3 &pos ); + virtual idVec3 GetEyePosition( void ) const; + virtual idVec3 TransformToPlayerSpace( const idVec3& origin ) const; + virtual idVec3 TransformToPlayerSpaceNotInterpolated( const idVec3& origin ) const; //rww + virtual idMat3 TransformToPlayerSpace( const idMat3& axis ) const; + virtual idAngles TransformToPlayerSpace( const idAngles& angles ) const; + virtual bool ShouldTouchTrigger( idEntity* entity ) const; + + virtual void EnterVehicle( hhVehicle* vehicle ); + virtual void ExitVehicle( hhVehicle* vehicle ); + virtual void ResetClipModel(); + hhPlayerVehicleInterface* GetVehicleInterfaceLocal() { return &vehicleInterfaceLocal; } + virtual void BecameBound(hhBindController *b); // Just attached to a bindController + virtual void BecameUnbound(hhBindController *b); // Just detached from a bindController + + virtual void Possess( idEntity* possessor ); // cjr + virtual void Unpossess(); // cjr + virtual bool CanBePossessed( void ); // cjr + void PossessKilled( void ); // cjr + + virtual idVec3 GetPortalPoint( void ); // cjr: The entity will portal when this point crosses the portal plane. Origin for most, eye location for players + virtual void Portalled( idEntity *portal ); + void SetPortalColliding( bool b ) { bCollidingWithPortal = b; } + bool IsPortalColliding( void ) { return bCollidingWithPortal; } + + virtual void UpdateModelTransform( void );//aob + + virtual void PlayFootstepSound();//aob + virtual void PlayPainSound();//aob + + bool IsCrouching() const { return physicsObj.IsCrouching(); } + virtual void ForceCrouching() { physicsObj.ForceCrouching(); }//aob + virtual void FillDebugVars( idDict *args, int page ); + virtual bool ChangingWeapons() const { return idealWeapon != currentWeapon; } + void BufferLoggedViewAngles( const idAngles& newUntransformedViewAngles ); + virtual void GetPilotInput( usercmd_t& pilotCmds, idAngles& pilotViewAngles ); + + void UpdateDDA(); // CJR DDA + + virtual void UpdateLocation( void ); + + // Unique Methods + void SetupWeaponInfo(); + void DialogStart(bool bDisallowPlayerDeath, bool bVoiceDucking, bool bLowerWeapon); + void DialogStop(); + void DoDeathDrop(); + void GetLocationText( idStr &locationString ); + void TrySpawnTalon(); + void TalonAttackComment(); // cjr - comment on Talon attacking + void RemoveResources(); + int GetCurrentWeapon( void ) const; + void SetCurrentWeapon( const int _currentWeapon ); + int GetIdealWeapon( void ) const; + void InvalidateCurrentWeapon() { currentWeapon = -1; } + virtual bool ShouldRemainAlignedToAxial() const { return physicsObj.ShouldRemainAlignedToAxial(); } + virtual idVec3 ApplyLandDeflect( const idVec3& pos, float scale ); + virtual idVec3 ApplyBobCycle( const idVec3& pos, const idVec3& velocity ); + virtual void DetermineOwnerPosition( idVec3 &ownerOrigin, idMat3 &ownerAxis ); + void StartSpiritWalk( const bool bThrust, bool force = false ); // mdl: Added force for when the player is possessed + void StopSpiritWalk(bool forceAllowance = false); //rww - added forceAllowance + void ToggleSpiritWalk( void ); + + int GetDeathWalkPower( void ) { return deathWalkPower; } // cjr: used for determining winning deathwalk + void SetDeathWalkPower( int newPower ) { deathWalkPower = newPower; } // cjr: used for determining winning deathwalk + + void ToggleLighter( void ); + void LighterOn(); + void LighterOff(); + bool IsLighterOn() const; //HUMANHEAD PCF mdl 05/04/06 - Made const + void UpdateLighter(); + void ThrowGrenade(); + void Event_ReturnToWeapon(); + void EnableEthereal( const char *proxyName, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + void DisableEthereal( void ); + void DisableSpiritWalk( int timeout ); // Number of seconds to disable ethereal mode + void GetResurrectionPoint( idVec3& origin, idMat3& axis, idVec3& viewDir, idAngles& angles, idMat3& eyeAxis, const idBounds& absBounds, const idVec3& defaultOrigin, const idMat3& defaultAxis, const idVec3& defaultViewDir, const idAngles& defaultAngles ); + void UpdatePossession( void ); // cjr + virtual void Kill( bool delayRespawn, bool nodamage ); // aob + void DeathWalk( const idVec3& resurrectOrigin, const idMat3& resurrectAxis, const idMat3& resurrectViewAxis, const idAngles& resurrectAngles, const idMat3& resurrectEyeAxis ); // cjr: entering deathwalk mode + idEntity * GetDeathwalkEnergyDestination(); + void DeathWraithEnergyArived(bool energyHealth); + void DeathWalkDamagedByWraith(idEntity *attacker, const char *damageType); + void DeathWalkSuccess(); + bool DeathWalkStage2() { return bDeathWalkStage2; } + void ReallyKilled( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); // cjr: truly dead (killed while in deathwalk mode) + void Resurrect( void ); + void KilledDeathWraith( void ); // cjr: called when the player kills a deathwraith (determines if the player should resurrect) + virtual bool IsDead() const { return (!bDeathWalk && health <= 0); } // True if the player is really dead + virtual bool IsSpiritOrDeathwalking() const { return ( bSpiritWalk || bDeathWalk ); } // True if the player is spiritwalking or deathwalking + virtual bool IsSpiritWalking() const { return ( bSpiritWalk && !bDeathWalk ); } // True if the player is spiritwalking, but not deathwalking + virtual bool IsDeathWalking() const { return bDeathWalk; } // True if the player is deathwalking + virtual bool IsPossessed() const { return bPossessed; } + void SnapDownCurrentWeapon(); + void SnapUpCurrentWeapon(); + void SelectEtherealWeapon(); + void PutawayEtherealWeapon(); + bool SkipWeapon( int weaponNum ) const; + hhWeapon* SpawnWeapon( const char* name ); + virtual bool InGUIMode( ); // nla + void CL_UpdateProgress(bool bBar, float value, int state); + int GetSpiritPower(); + void SetSpiritPower(int amount); + + void ResetZoomFov() { zoomFov.Init( gameLocal.GetTime(), 0, g_fov.GetFloat(), g_fov.GetFloat() ); } + virtual float CalcFov( bool honorZoom ); + virtual void MangleControls(usercmd_t *cmd); + virtual void UpdateCrosshairs( void ); + + void SetOverlayGui(const char *guiName); + + void InCinematic( bool cinematic ) { bInCinematic = cinematic; } + bool InCinematic() const { return bInCinematic; } + + const char * GetGuiHandInfo(); // nla + + idInterpolate& GetZoomFov() { return zoomFov; } + + void SetViewBob(const idVec3 &v) {viewBob = v;} + int GetLastResurrectTime(void) const {return lastResurrectTime;} // Time stamp indicating when the player last was resurrected + virtual idVec3 GetAimPosition() const; // jrm + + void SetViewAnglesSensitivity( float factor ); + float GetViewAnglesSensitivity() const; + + //? Is there a better way of doing this? + // Force the weapon num... + virtual void ForceWeapon( int weaponNum ); + virtual void ForceWeapon( hhWeapon *newWeapon ); + void Freeze( float unfreezeDelay = 0.0f ); + void Unfreeze(void); + bool IsFrozen() { return bFrozen; }; + + void LockWeapon( int weaponNum ); + void UnlockWeapon( int weaponNum ); + bool IsLocked( int weaponNum ); + hhSpiritProxy* GetSpiritProxy() { return spiritProxy.IsValid() ? spiritProxy.GetEntity() : NULL; } + + //rww - instead of doing this through the death proxy, this is safer (being an af, the death proxy could be oriented strangely or removed in numerous ways) + virtual void RestorePlayerLocationFromDeathwalk( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ); + + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + int mpHitFeedbackTime; //HUMANHEAD rww + + virtual void Show(); + +protected: + idUserInterface * guiOverlay; + idClipModel thirdPersonCameraClipBounds; + float viewAnglesSensitivity; + int lastResurrectTime; + int spiritDrainHeartbeatMS; + int ddaHeartbeatMS; + int spiritWalkToggleTime; + bool bDeathWalkStage2; + bool bFrozen; + int weaponFlags; // Flags to limit weapons on specific level -mdl + int nextSpiritTime; // Disable ethereal mode unless gameLocal.time is greater than this + idInterpolateAccelDecelSine cinematicFOV; // HUMANHEAD rdr: Interpolated FOV for scripter use + bool bAllowSpirit; + + bool bCollidingWithPortal; // CJR + bool bLotaTunnelMode; // Used to disable hud during LOTA tunnel transitions + + // HUMANHEAD: aob + idAngles untransformedViewAngles; + idMat3 untransformedViewAxis; + // HUMANHEAD END + + //HUMANHEAD rww + idVec3 deathwalkLastOrigin; + idMat3 deathwalkLastBBoxAxis; + idMat3 deathwalkLastViewAxis; + idAngles deathwalkLastViewAngles; + idMat3 deathwalkLastEyeAxis; + bool deathwalkLastCrouching; + //HUMANHEAD END + + // Overridden Methods + virtual int GetMaxHealth() { return inventory.maxHealth; } + + // Unique Methods + virtual void FireWeapon( void ); + virtual void FireWeaponAlt( void ); + + void StopFiring( void ); + + void SetupWeaponFlags( void ); + + void Event_LotaTunnelMode(bool on); + void Event_PlayWeaponAnim( const char* animName, int numTries = 0 ); + void Event_DrainSpiritPower(); // HUMANHEAD pdm + + void Event_RechargeHealth( void ); // cjr + void Event_RechargeRifleAmmo( void ); // cjr + + void Event_SpawnDeathWraith(); // cjr + void Event_AdjustSpiritPowerDeathWalk(); + void Event_PrepareForDeathWorld(); + void Event_EnterDeathWorld(); // cjr: set-up for teleporting the player to the deathworld + void Event_PrepareToResurrect(); + void Event_ResurrectScreenFade(); + void Event_Resurrect(); + virtual void Event_GetSpiritPower(); //rww + virtual void Event_SetSpiritPower(const float s); //rww + void Event_OnGround(); // bg + + void Event_Cinematic( int on, int lockView ); // pdm + void Event_DialogStart( int bDisallowPlayerDeath, int bVoiceDucking, int bLowerWeapon ); + void Event_DialogStop(); + void Event_ShouldRemainAlignedToAxial( bool remainAligned ); + void Event_OrientToGravity( bool orient ); + virtual void Event_ResetGravity(); + void Event_SetOverlayMaterial( const char *mtrName, const int requiresScratch ); + void Event_SetOverlayTime( const float newTime, const int requiresScratch ); + void Event_SetOverlayColor( const float r, const float g, const float b, const float a ); + + void Event_DDAHeartBeat(); + void Event_StartHUDTranslation(); + void Event_Unfreeze(); + void Event_LockWeapon( int weaponNum ); + void Event_UnlockWeapon( int weaponNum ); + + void Event_SetPrivateCameraView( idEntity *camView, int noHide ); //HUMANHEAD rdr + void Event_SetCinematicFOV( float fieldOfView, float accelTime, float decelTime, float duration ); //HUMANHEAD rdr + void Event_StopSpiritWalk(); //rww + void Event_DamagePlayer(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, char *damageDefName, float damageScale, int location); //rww + void Event_GetSpiritProxy(); + void Event_IsSpiritWalking(); + void Event_IsDeathWalking(); //bjk + void Event_GetDDAValue(); // cjr + void Event_AllowLighter( bool allow ); + void Event_DisableSpirit(); + void Event_EnableSpirit(); + void Event_CanAnimateTorso(void); //HUMANHEAD rww + + void Event_UpdateDDA(); // cjr + + void Event_AllowDamage(); // mdl + void Event_IgnoreDamage(); // mdl + + void Event_RespawnCleanup(void); //HUMANHEAD rww +}; + +/* +===================== +hhPlayer::SetEyeAxis +===================== +*/ +ID_INLINE void hhPlayer::SetEyeAxis( const idMat3 &axis ) { + if (!cameraInterpolator.GetIdealAxis().Compare(axis)) { + cameraInterpolator.SetTargetAxis( axis, INTERPOLATE_EYEOFFSET ); + } +} + +/* +===================== +hhPlayer::SetEyeHeight +===================== +*/ +ID_INLINE void hhPlayer::SetEyeHeight( float height ) { + idPlayer::SetEyeHeight( height ); + + cameraInterpolator.SetTargetEyeOffset( height, INTERPOLATE_EYEOFFSET ); +} + +/* +===================== +hhPlayer::EyeHeight +===================== +*/ +ID_INLINE float hhPlayer::EyeHeight( void ) const { + return cameraInterpolator.GetCurrentEyeHeight(); +} + +/* +===================== +hhPlayer::EyeOffset +===================== +*/ +ID_INLINE idVec3 hhPlayer::EyeOffset( void ) const {//HUMANHEAD + return cameraInterpolator.GetCurrentEyeOffset(); +} + +/* +===================== +hhPlayer::ShouldRemainAlignedToAxial + HUMANHEAD +===================== +*/ +ID_INLINE void hhPlayer::ShouldRemainAlignedToAxial( bool remainAligned ) {//HUMANHEAD + physicsObj.ShouldRemainAlignedToAxial( remainAligned ); +} + +/* +===================== +hhPlayer::OrientToGravity + HUMANHEAD +===================== +*/ +ID_INLINE void hhPlayer::OrientToGravity( bool orientToGravity ) {//HUMANHEAD + physicsObj.OrientToGravity( orientToGravity ); +} + +/* +===================== +hhPlayer::EyePosition + HUMANHEAD +===================== +*/ +ID_INLINE idVec3 hhPlayer::GetEyePosition( void ) const {//HUMANHEAD + return cameraInterpolator.GetEyePosition(); +} + +/* +===================== +hhPlayer::GetEyeAxis + HUMANHEAD +===================== +*/ +ID_INLINE idMat3 hhPlayer::GetEyeAxis() const { + return cameraInterpolator.GetCurrentAxis(); +} + +/* +===================== +hhPlayer::GetEyeAxisAsAngles + HUMANHEAD +===================== +*/ +ID_INLINE idAngles hhPlayer::GetEyeAxisAsAngles() const { + return cameraInterpolator.GetCurrentAngles(); +} + +/* +===================== +hhPlayer::GetEyeAxisAsQuat + HUMANHEAD +===================== +*/ +ID_INLINE idQuat hhPlayer::GetEyeAxisAsQuat() const { + return cameraInterpolator.GetCurrentRotation(); +} + +/* +===================== +hhPlayer::GetStepHeight + HUMANHEAD +===================== +*/ +ID_INLINE float hhPlayer::GetStepHeight() const { + if( IsWallWalking() ) { + return pm_wallwalkstepsize.GetFloat(); + } + + return pm_stepsize.GetFloat(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpace + HUMANHEAD +===================== +*/ +ID_INLINE idVec3 hhPlayer::TransformToPlayerSpace( const idVec3& origin ) const { + return cameraInterpolator.GetCurrentPosition() + origin * GetEyeAxis(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpaceNotInterpolated + HUMANHEAD rww +===================== +*/ +ID_INLINE idVec3 hhPlayer::TransformToPlayerSpaceNotInterpolated( const idVec3& origin ) const { + return cameraInterpolator.GetIdealPosition() + origin * cameraInterpolator.GetIdealAxis(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpace + HUMANHEAD +===================== +*/ +ID_INLINE idMat3 hhPlayer::TransformToPlayerSpace( const idMat3& axis ) const { + return axis * GetEyeAxis(); +} + +/* +===================== +hhPlayer::TransformToPlayerSpace + HUMANHEAD +===================== +*/ +ID_INLINE idAngles hhPlayer::TransformToPlayerSpace( const idAngles& angles ) const { + return (angles.ToMat3() * GetEyeAxis()).ToAngles(); +} + +/* +===================== +hhPlayer::GetCurrentWeapon + HUMANHEAD +===================== +*/ +ID_INLINE int hhPlayer::GetCurrentWeapon( void ) const { + return currentWeapon; +} + +/* +===================== +hhPlayer::SetCurrentWeapon + HUMANHEAD +===================== +*/ +ID_INLINE void hhPlayer::SetCurrentWeapon( const int _currentWeapon ) { + if( _currentWeapon >= 0 && _currentWeapon < MAX_WEAPONS ) { + currentWeapon = _currentWeapon; + } +} + +/* +===================== +hhPlayer::GetIdealWeapon + HUMANHEAD +===================== +*/ +ID_INLINE int hhPlayer::GetIdealWeapon( void ) const { + return idealWeapon; +} + +/* +===================== +hhPlayer::GetUntransformedViewAngles + HUMANHEAD +===================== +*/ +ID_INLINE const idAngles& hhPlayer::GetUntransformedViewAngles() const { + return untransformedViewAngles; +} + +/* +===================== +hhPlayer::GetUntransformedViewAxis + HUMANHEAD +===================== +*/ +ID_INLINE const idMat3& hhPlayer::GetUntransformedViewAxis() const { + return untransformedViewAxis; +} + +/* +===================== +hhPlayer::GetViewAngles + HUMANHEAD +===================== +*/ +ID_INLINE idAngles hhPlayer::GetViewAngles() const { + return viewAngles; +} + +/* +===================== +hhPlayer::GetPortalPoint + HUMANHEAD +===================== +*/ +ID_INLINE idVec3 hhPlayer::GetPortalPoint( void ) { + return idEntity::GetPortalPoint(); +/* + idVec3 origin; + idMat3 axis; + + GetViewPos( origin, axis ); + return origin; // Compensate for the near-clip plane +*/ +} + +//HUMANHEAD rww +class hhArtificialPlayer : public hhPlayer { + CLASS_PROTOTYPE(hhArtificialPlayer); +public: + hhArtificialPlayer(void); + + void Spawn( void ); + virtual void Think( void ); + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + usercmd_t lastAICmd; + + bool testCrouchActive; + int testCrouchTime; +}; +//HUMANHEAD END +#endif /* __PREY_GAME_PLAYER_H__ */ diff --git a/neo/prey/Prey/game_playerview.cpp b/neo/prey/Prey/game_playerview.cpp new file mode 100644 index 00000000..61b10a46 --- /dev/null +++ b/neo/prey/Prey/game_playerview.cpp @@ -0,0 +1,748 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define LETTERBOX_HEIGHT_TOP 50 +#define LETTERBOX_HEIGHT_BOTTOM 50 + +const int IMPULSE_DELAY = 150; + +//HUMANHEAD rww - render demo madness +#if _HH_RENDERDEMO_HACKS + void RENDER_DEMO_VIEWRENDER(const renderView_t *view, const hhPlayerView *pView) { + if (!view) { + return; + } + + const renderView_t *v = view; + + if (pView) { + static renderView_t hackedView = *view; + hackedView.viewaxis = hackedView.viewaxis * pView->ShakeAxis(); + + v = &hackedView; + } + + renderSystem->LogViewRender(v); + } + + void RENDER_DEMO_VIEWRENDER_END(void) { + renderSystem->LogViewRender(NULL); + } +#endif +//HUMANHEAD END + +hhPlayerView::hhPlayerView() { + // HUMANHEAD pdm: we don't use the tunnel vision or armor + bLetterBox = false; + letterboxMaterial = declManager->FindMaterial( "_black" ); + dirDmgLeftMaterial = declManager->FindMaterial( "textures/interface/directionalDamageLeft" ); + dirDmgFrontMaterial = declManager->FindMaterial( "textures/interface/directionalDamageFront" ); + spiritMaterial = NULL; + viewOverlayMaterial = NULL; + viewOverlayColor = colorWhite; + voTotalTime = -1; + voRequiresScratchBuffer = false; + viewOffset.Zero(); + + kickSpeed.Zero(); + kickLastTime = 0; + hurtValue = 100.f; +} + +void hhPlayerView::Save(idSaveGame *savefile) const { + idPlayerView::Save( savefile ); + + savefile->WriteBool( bLetterBox ); + savefile->WriteFloat( mbAmplitude ); + savefile->WriteInt( mbFinishTime ); + savefile->WriteInt( mbTotalTime ); + savefile->WriteVec3( mbDirection ); + savefile->WriteVec3( viewOffset ); + savefile->WriteMaterial( viewOverlayMaterial ); + savefile->WriteVec4( viewOverlayColor ); + savefile->WriteInt( voFinishTime ); + savefile->WriteInt( voTotalTime ); + savefile->WriteInt( voRequiresScratchBuffer ); + savefile->WriteMaterial( letterboxMaterial ); + savefile->WriteMaterial( dirDmgLeftMaterial ); + savefile->WriteMaterial( dirDmgFrontMaterial ); + savefile->WriteMaterial( spiritMaterial ); + savefile->WriteVec3( lastDamageLocation ); + savefile->WriteInt( kickLastTime - gameLocal.time ); + savefile->WriteAngles( kickSpeed ); + savefile->WriteFloat( hurtValue ); +} + +void hhPlayerView::Restore( idRestoreGame *savefile ) { + idPlayerView::Restore( savefile ); + + savefile->ReadBool( bLetterBox ); + savefile->ReadFloat( mbAmplitude ); + savefile->ReadInt( mbFinishTime ); + savefile->ReadInt( mbTotalTime ); + savefile->ReadVec3( mbDirection ); + savefile->ReadVec3( viewOffset ); + savefile->ReadMaterial( viewOverlayMaterial ); + savefile->ReadVec4( viewOverlayColor ); + savefile->ReadInt( voFinishTime ); + savefile->ReadInt( voTotalTime ); + savefile->ReadInt( voRequiresScratchBuffer ); + savefile->ReadMaterial( letterboxMaterial ); + savefile->ReadMaterial( dirDmgLeftMaterial ); + savefile->ReadMaterial( dirDmgFrontMaterial ); + savefile->ReadMaterial( spiritMaterial ); + savefile->ReadVec3( lastDamageLocation ); + savefile->ReadInt( kickLastTime ); + kickLastTime += gameLocal.time; + savefile->ReadAngles( kickSpeed ); + savefile->ReadFloat( hurtValue ); +} + +#define MAX_SCREEN_BLOBS_SYNC 2//MAX_SCREEN_BLOBS + +//rwwFIXME it may be possible to remove a lot of this junk by assuring events happen on the client as they do on the server +//concerning damage etc. +void hhPlayerView::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat(viewOffset.x); + msg.WriteFloat(viewOffset.y); + msg.WriteFloat(viewOffset.z); + + msg.WriteBits(voTotalTime, 32); + msg.WriteBits(voFinishTime, 32); + msg.WriteBits(voRequiresScratchBuffer, 1); + + if (viewOverlayMaterial) { + msg.WriteLong(gameLocal.ServerRemapDecl(-1, DECL_MATERIAL, viewOverlayMaterial->Index())); + } + else { + msg.WriteLong(-1); + } + + //write screen blobs to snapshot + for (int i = 0; i < MAX_SCREEN_BLOBS_SYNC; i++) { + const screenBlob_t *blob = &screenBlobs[i]; + + //i'm disabling this since it might work against delta compression being effective + /* + if ( blob->finishTime <= gameLocal.time ) { + msg.WriteBits(0, 1); //no need to do anymore, continue + continue; + } + + //otherwise continue writing + msg.WriteBits(1, 1); + */ + + if (blob->material) { + msg.WriteLong(gameLocal.ServerRemapDecl(-1, DECL_MATERIAL, blob->material->Index())); + } + else { + msg.WriteLong(-1); + } + + msg.WriteFloat(blob->x); + msg.WriteFloat(blob->y); + msg.WriteFloat(blob->w); + msg.WriteFloat(blob->h); + + msg.WriteFloat(blob->s1); + msg.WriteFloat(blob->t1); + msg.WriteFloat(blob->s2); + msg.WriteFloat(blob->t2); + + msg.WriteBits(blob->finishTime, 32); + msg.WriteBits(blob->startFadeTime, 32); + + msg.WriteFloat(blob->driftAmount); + } + + //motion blur + msg.WriteFloat(mbAmplitude); + msg.WriteBits(mbFinishTime, 32); + msg.WriteBits(mbTotalTime, 32); + msg.WriteFloat(mbDirection.x); + msg.WriteFloat(mbDirection.y); + msg.WriteFloat(mbDirection.z); + + msg.WriteFloat(viewOverlayColor.w); + msg.WriteFloat(viewOverlayColor.x); + msg.WriteFloat(viewOverlayColor.y); + msg.WriteFloat(viewOverlayColor.z); +} + +void hhPlayerView::ReadFromSnapshot( const idBitMsgDelta &msg ) { + viewOffset.x = msg.ReadFloat(); + viewOffset.y = msg.ReadFloat(); + viewOffset.z = msg.ReadFloat(); + + voTotalTime = msg.ReadBits(32); + voFinishTime = msg.ReadBits(32); + voRequiresScratchBuffer = !!msg.ReadBits(1); + + int matIndex = msg.ReadLong(); + if (matIndex == -1) { + viewOverlayMaterial = NULL; + } + else { + int mappedIndex; + + if (voTotalTime != -1 && gameLocal.time >= voFinishTime) { //it's timed out so force it off + voTotalTime = -1; + voRequiresScratchBuffer = false; + viewOverlayMaterial = NULL; + mappedIndex = -1; + } + else { + mappedIndex = gameLocal.ClientRemapDecl(DECL_MATERIAL, matIndex); + } + + if (mappedIndex != -1) { + viewOverlayMaterial = static_cast(declManager->DeclByIndex(DECL_MATERIAL, mappedIndex)); + } + else { + viewOverlayMaterial = NULL; + } + } + + //read screen blobs from snapshot + for (int i = 0; i < MAX_SCREEN_BLOBS_SYNC; i++) { + screenBlob_t *blob = &screenBlobs[i]; + + //i'm disabling this since it might work against delta compression being effective + /* + bool valid = !!msg.ReadBits(1); + if (!valid) { + continue; + } + */ + + int blobMat = msg.ReadLong(); + if (blobMat == -1) { + blob->material = NULL; + } + else { + int mappedIndex = gameLocal.ClientRemapDecl(DECL_MATERIAL, blobMat); + if (mappedIndex != -1) { + blob->material = static_cast(declManager->DeclByIndex(DECL_MATERIAL, mappedIndex)); + } + else { + blob->material = NULL; + } + } + + blob->x = msg.ReadFloat(); + blob->y = msg.ReadFloat(); + blob->w = msg.ReadFloat(); + blob->h = msg.ReadFloat(); + + blob->s1 = msg.ReadFloat(); + blob->t1 = msg.ReadFloat(); + blob->s2 = msg.ReadFloat(); + blob->t2 = msg.ReadFloat(); + + blob->finishTime = msg.ReadBits(32); + blob->startFadeTime = msg.ReadBits(32); + + blob->driftAmount = msg.ReadFloat(); + } + + //motion blur + mbAmplitude = msg.ReadFloat(); + mbFinishTime = msg.ReadBits(32); + mbTotalTime = msg.ReadBits(32); + mbDirection.x = msg.ReadFloat(); + mbDirection.y = msg.ReadFloat(); + mbDirection.z = msg.ReadFloat(); + + viewOverlayColor.w = msg.ReadFloat(); + viewOverlayColor.x = msg.ReadFloat(); + viewOverlayColor.y = msg.ReadFloat(); + viewOverlayColor.z = msg.ReadFloat(); +} + +void hhPlayerView::ClearEffects() { + idPlayerView::ClearEffects(); + + // HUMANHEAD pdm + mbFinishTime = gameLocal.time; +} + + +void hhPlayerView::SetDamageLoc(const idVec3 &damageLoc) { + // This called before damageImpulse() so store the location + lastDamageLocation = damageLoc; +} + + +//------------------------------------------------------ +// +// DamageImpulse +// +// LocalKickDir is the direction of force in the player's coordinate system, +// which will determine the head kick direction +//------------------------------------------------------ +void hhPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef ) { + + // No screen damage effects when in third person + if (pm_thirdPerson.GetBool()) { + return; + } + + if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.time ) { + // keep shotgun from obliterating the view + return; + } + + if (!player->InVehicle()) { + // + // Motion Blur effect + // HUMANHEAD pdm + float mbTime = damageDef->GetFloat( "mb_time" ); + float severity = damageDef->GetFloat( "mb_amplitude" ); + if ( mbTime ) { + idVec3 blurDirection; + blurDirection.y = localKickDir[0]; // forward/back kick will blur vertically + blurDirection.x = localKickDir[1]; // side kick will blur horizontally + blurDirection.y += localKickDir[2]; // up/down kick will add to vertical + MotionBlur(mbTime, severity, blurDirection); + } + + // + // head angle kick + // + 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; + } + } + + // + // HUMANHEAD: Screen blobs, changed functionality + // + 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 ); + + // Scale blob by 100% +/- blob_devscale% + float scale = ( 256.0f + ( 256.0f*damageDef->GetFloat("blob_devscale")*gameLocal.random.CRandomFloat() ) ) / 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; + + if (damageDef->GetBool("blob_projected") && player->renderView) { + //rww - renderView null check added. I am not clear on when this may be null, but it apparently was. however, if a player were + //to take damage before thinking, it seems very possible this would occur. the renderView will not be initialized until after + //the first think. + // Project hit location onto screen + idVec3 localDamageLocation = hhUtils::ProjectOntoScreen(lastDamageLocation, *player->renderView); + blob->x = localDamageLocation.x - blob->w * 0.5f; + blob->y = localDamageLocation.y - blob->h * 0.5f; + } + else if (damageDef->GetBool("blob_directional")) { + // Directional blobs to show where damage is coming from (360 degree) + float dirDist = damageDef->GetFloat("blob_dirdistance"); + blob->x = 320.0f + (dirDist * 320.0f * localKickDir.y) - (blob->w * 0.5f); + blob->y = 240.0f + (dirDist * 240.0f * localKickDir.x) - (blob->h * 0.5f); + } + else { + // Place blob centered at (blob_x,blob_y) + blob->x = damageDef->GetFloat( "blob_x" ) - blob->w * 0.5f; + blob->y = damageDef->GetFloat( "blob_y" ) - blob->h * 0.5f; + } + + // Deviate blob +/- (blob_devx,blob_devy) + blob->x += damageDef->GetFloat( "blob_devx" ) * gameLocal.random.CRandomFloat(); + blob->y += damageDef->GetFloat( "blob_devy" ) * gameLocal.random.CRandomFloat(); + } + } + + + // + // Global directional damage system + // +/* + const int directionDamageTime = 1000; + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + directionDamageTime; + + blob->s1 = 0; + blob->t1 = 0; + blob->s2 = 1; + blob->t2 = 1; + if (idMath::Fabs(localKickDir[0]) >= idMath::Fabs(localKickDir[1])) { + // More in X direction + blob->material = dirDmgFrontMaterial; + blob->w = 500.0f; + blob->h = 80.0f; + blob->x = 320.0f - blob->w * 0.5f; + if (localKickDir[0] >= 0.0f) { + // From Rear + blob->y = 480.0f - blob->h; + idSwap(blob->t1, blob->t2); + } + else { + // From Front + blob->y = 0.0f; + } + } + else { + // More in Y direction + blob->material = dirDmgLeftMaterial; + blob->w = 80.0f; + blob->h = 400.0f; + blob->y = 240.0f - blob->h * 0.5f; + if (localKickDir[1] >= 0.0f) { + // From Right + blob->x = 640.0f - blob->w; + idSwap(blob->s1, blob->s2); + } + else { + // From Left + blob->x = 0.0f; + } + } +*/ + + // + // save lastDamageTime for tunnel vision accentuation + // + lastDamageTime = MS2SEC( gameLocal.time ); + +} + +//------------------------------------------------------ +// WeaponFireFeedback +// HUMANHEAD bjk +//------------------------------------------------------ +void hhPlayerView::WeaponFireFeedback( const idDict *weaponDef ) { + idAngles angles; + idVec2 pitch, yaw, viewSpring; + weaponDef->GetVec2( "recoilPitch", "0 0", pitch ); + weaponDef->GetVec2( "recoilYaw", "0 0", yaw ); + weaponDef->GetVec2( "viewSpring", "150 11", viewSpring ); + + player->kickSpring = viewSpring.x; + player->kickDamping = viewSpring.y; + + pitch.x = pitch.x + gameLocal.random.RandomFloat()*(pitch.y - pitch.x); + yaw.x = yaw.x + gameLocal.random.RandomFloat()*(yaw.y - yaw.x); + angles=idAngles(-pitch.x,-yaw.x,0); + kickSpeed+=angles; + assert(!FLOAT_IS_NAN(kickSpeed.pitch) && !FLOAT_IS_NAN(kickSpeed.yaw) && !FLOAT_IS_NAN(kickSpeed.roll)); +} + +//------------------------------------------------------ +// AngleOffset +// HUMANHEAD bjk +//------------------------------------------------------ +idAngles hhPlayerView::AngleOffset(float kickSpring, float kickDamping) { + //HUMANHEAD rww - for other clients, do not add angle offset, it isn't predicted well + if (gameLocal.isMultiplayer && !gameLocal.isServer && gameLocal.localClientNum != -1 && player && gameLocal.localClientNum != player->entityNumber) { + kickSpeed.Zero(); + kickAngles.Zero(); + return kickAngles; + } + //HUMANHEAD END + + float frametime = (gameLocal.time - kickLastTime); + kickLastTime = gameLocal.time; + //kickAngles = kickAngles*(1-offset*kickSpeed)+kickBlendTo*offset*kickSpeed; + //kickBlendTo = kickBlendTo*(1-offset*kickReturnSpeed)+ang*offset*kickReturnSpeed; + + //HUMANHEAD PCF rww 04/27/06 - these values can get unreasonable at times +#if 0 + for (int i = 0; i < 3; i++) { + if (fabsf(kickSpeed[i]) > 9999.0f) { + kickSpeed[i] = 0.0f; + } + if (fabsf(kickAngles[i]) > 9999.0f) { + kickAngles[i] = 0.0f; + } + } +#endif + if (frametime > 48.0f) { + frametime = 48.0f; + } + //HUMANHEAD END + + assert(!FLOAT_IS_NAN(kickSpeed.pitch) && !FLOAT_IS_NAN(kickSpeed.yaw) && !FLOAT_IS_NAN(kickSpeed.roll)); + assert(!FLOAT_IS_NAN(kickAngles.pitch) && !FLOAT_IS_NAN(kickAngles.yaw) && !FLOAT_IS_NAN(kickAngles.roll)); + + kickSpeed-=kickDamping*kickSpeed*frametime/1000; + kickSpeed-=kickSpring*kickAngles*frametime/1000; + + if (gameLocal.isNewFrame) { //HUMANHEAD rww + kickAngles+=kickSpeed*frametime/1000; + } + + return kickAngles; +} + +//------------------------------------------------------ +// SingleView +//------------------------------------------------------ +void hhPlayerView::SingleView( idUserInterface *hud, const renderView_t *view ) { + // normal rendering + + if ( !view ) { + return; + } + + // place the sound origin for the player + gameSoundWorld->PlaceListener( view->vieworg, view->viewaxis, player->entityNumber + 1, gameLocal.time, hud ? hud->State().GetString( "location" ) : "Undefined" ); + + // hack the shake in at the very last moment, so it can't cause any consistency problems + renderView_t hackedView = *view; + hackedView.viewaxis = hackedView.viewaxis * ShakeAxis(); + + gameRenderWorld->RenderScene( &hackedView ); + + if ( player->spectating ) { + 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 ); + } + } + + // HUMANHEAD: CJR + if ( voTotalTime != -1 ) { // Check if the viewOverlay should time out + if ( gameLocal.time >= voFinishTime ) { + voTotalTime = -1; + voRequiresScratchBuffer = false; + viewOverlayMaterial = NULL; // Remove the overlay + } + } + + //HUMANHEAD: aob + if( viewOverlayMaterial ) { + renderSystem->SetColor4( viewOverlayColor[0], viewOverlayColor[1], viewOverlayColor[2], viewOverlayColor[3] ); + renderSystem->DrawStretchPic(0, 0, 640, 480, 0, 1, 1, 0, viewOverlayMaterial); + } + //HUMANHEAD END + + if ( player->health > 0 && player->health < 25 && static_cast(player)->IsSpiritOrDeathwalking()==false ) { + hurtValue += 0.05f*(player->health - hurtValue); + if( player->health > 30 ) + hurtValue = player->health; + + renderSystem->SetColor4( hurtValue/25.0f, 1, 1, 1 ); + renderSystem->DrawStretchPic(0, 0, 640, 480, 0, 1, 1, 0, hurtMaterial); + } + } + + // If this level has a sun corona, then attempt to draw it - CJR + if ( !g_skipViewEffects.GetBool() && gameLocal.GetSunCorona() ) { + gameLocal.GetSunCorona()->Draw( (hhPlayer *)player ); + } + + // 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 ); + } + } +} + +// Hermite() +// Hermite Interpolator +// Returns an alpha value [0..1] based on Hermite Parameters N1, N2, S1, S2 and an input alpha 't' +float Hermite(float t, float N1, float N2, float S1, float S2) { + float tSquared = t*t; + float tCubed = tSquared*t; + return (2*tCubed - 3*tSquared + 1)*N1 + + (-2*tCubed + 3*tSquared)*N2 + + (tCubed - 2*tSquared + t)*S1 + + (tCubed - tSquared)*S2; +} + + +//------------------------------------------------------ +// MotionBlurVision +// HUMANHEAD pdm +//------------------------------------------------------ + +void hhPlayerView::MotionBlurVision(idUserInterface *hud, const renderView_t *view) { + if ( !g_doubleVision.GetBool() ) { + SingleView( hud, view ); + return; + } + + float alpha; + const float N1 = 0.2f; + const float N2 = 0.0f; + const float S1 = 1.0f; + const float S2 = 1.0f; + + float remainingTime = mbFinishTime - gameLocal.time; + float elapsedTime = mbTotalTime - remainingTime; + float scale = remainingTime / mbTotalTime; + + // Render to a texture + RENDER_DEMO_VIEWRENDER(view, this); //HUMANHEAD rww + renderSystem->CropRenderSize( 512, 256, true ); + SingleView( hud, view ); + renderSystem->CaptureRenderToImage( "_scratch" ); + renderSystem->UnCrop(); + RENDER_DEMO_VIEWRENDER_END(); //HUMANHEAD rww + + // Motion blur + float xshift, yshift; + for (int index=0; index1?1:(a)) + renderSystem->SetColor4( 1,1,1, index==0 ? 1.0f : 0.2f ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, + CLIP(xshift), CLIP(1+yshift), CLIP(1+xshift), CLIP(yshift), scratchMaterial ); // clipped + } +} + +//------------------------------------------------------ +// SpiritVision +// HUMANHEAD cjr +//------------------------------------------------------ + +void hhPlayerView::SpiritVision( idUserInterface *hud, const renderView_t *view ) { + int oldTime = voTotalTime; + const idMaterial *oldMaterial = viewOverlayMaterial; + + voTotalTime = -1; + viewOverlayMaterial = NULL; + + SingleView( hud, view ); + + // Draw the spiritwalk image over the top + if ( player && !spiritMaterial ) { + spiritMaterial = declManager->FindMaterial( player->spawnArgs.GetString( "mtr_spiritwalk" ) ); + } + + renderSystem->SetColor4( 1, 1, 1, 1 ); + renderSystem->DrawStretchPic(0, 0, 640, 480, 0, 1, 1, 0, spiritMaterial ); + + voTotalTime = oldTime; + viewOverlayMaterial = oldMaterial; +} + +//------------------------------------------------------ +// ApplyLetterbox +// HUMANHEAD pdm +//------------------------------------------------------ +void hhPlayerView::ApplyLetterBox(const renderView_t *view) { + if (bLetterBox) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic(0, 0, 640, LETTERBOX_HEIGHT_TOP, 0, 0, 1, 1, letterboxMaterial); + renderSystem->DrawStretchPic(0, 480-LETTERBOX_HEIGHT_BOTTOM, 640, LETTERBOX_HEIGHT_BOTTOM, 0, 0, 1, 1, letterboxMaterial); + } +} + +//------------------------------------------------------ +// MotionBlur +// HUMANHEAD pdm +//------------------------------------------------------ +void hhPlayerView::MotionBlur(int mbTime, float severity, idVec3 &direction) { + mbTotalTime = mbTime; + mbFinishTime = gameLocal.time + mbTotalTime; + mbAmplitude = severity; + mbDirection = direction; +} + +//------------------------------------------------------ +// SetLetterBox +// HUMANHEAD pdm +//------------------------------------------------------ +void hhPlayerView::SetLetterBox(bool on) { + bLetterBox = on; +} + +//------------------------------------------------------ +// RenderPlayerView +//------------------------------------------------------ +void hhPlayerView::RenderPlayerView( idUserInterface *hud ) { + const renderView_t *view = player->GetRenderView(); + + if ( g_skipViewEffects.GetBool() ) { + SingleView( hud, view ); + } else { + if (gameLocal.time < mbFinishTime) { + MotionBlurVision(hud, view); + } else if ( ((hhPlayer *)player)->IsSpiritWalking() ) { + SpiritVision( hud, view ); + } else { + SingleView(hud, view); + } + ScreenFade(); + + // HUMANHEAD pdm: letterbox + ApplyLetterBox(view); + } + + // HUMANHEAD: Draw the HUD after all over overlay effects. + if (hud) { + hud->SetStateBool("letterbox", bLetterBox); + player->DrawHUD( hud ); + } + + if ( net_clientLagOMeter.GetBool() && lagoMaterial && gameLocal.isClient ) { + 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 ); + } +} diff --git a/neo/prey/Prey/game_playerview.h b/neo/prey/Prey/game_playerview.h new file mode 100644 index 00000000..d64b6a2e --- /dev/null +++ b/neo/prey/Prey/game_playerview.h @@ -0,0 +1,108 @@ +#ifndef __PREY_GAME_PLAYERVIEW_H__ +#define __PREY_GAME_PLAYERVIEW_H__ + +class hhPlayerView : public idPlayerView { +public: + hhPlayerView(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClearEffects( void ); + virtual void DamageImpulse( idVec3 localKickDir, const idDict *damageDef ); + virtual void RenderPlayerView( idUserInterface *hud ); + + void WeaponFireFeedback( const idDict *weaponDef ); // HUMANHEAD bjk + idAngles AngleOffset( float kickSpeed, float kickReturnSpeed ); // HUMANHEAD bjk + + // HUMANHEAD + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void MotionBlur(int mbTime, float severity, idVec3 &direction); + idVec3 ViewOffset() { return viewOffset; }//HUMANHEAD: aob + void SetViewOverlayMaterial( const idMaterial* material, int scratchBuffer = false ); + void SetViewOverlayColor( idVec4 color ); // HUMANHEAD cjr + void SetViewOverlayTime( int time, int scratchBuffer = false ); + void SetLetterBox(bool on); + void SetDamageLoc(const idVec3 &damageLoc); + // HUMANHEAD END + +protected: + virtual void SingleView(idUserInterface *hud, const renderView_t *view); + + // HUMANHEAD pdm + void MotionBlurVision(idUserInterface *hud, const renderView_t *view); + void ApplyLetterBox(const renderView_t *view); + void SpiritVision( idUserInterface *hud, const renderView_t *view ); + +protected: + bool bLetterBox; // HUMANHEAD pdm: whether we are in letterbox mode + float mbAmplitude; + int mbFinishTime; + int mbTotalTime; + idVec3 mbDirection; + int kickLastTime; // HUMANHEAD bjk + idAngles kickSpeed; // HUMANHEAD bjk + idVec3 viewOffset; // HUMANHEAD: aob + const idMaterial * viewOverlayMaterial; // HUMANHEAD: aob + idVec4 viewOverlayColor; // HUMANHEAD cjr: Used to pass colors/parms to the view overlay + int voFinishTime; // HUMANHEAD cjr: Used to set the time that the view overlay stays on + int voTotalTime; // HUMANHEAD cjr: Used to set the time that the view overlay stays on + int voRequiresScratchBuffer;// HUMANHEAD cjr: requires the screen rendered to the scratch buffer + const idMaterial *letterboxMaterial; // HUMANHEAD pdm + const idMaterial *dirDmgLeftMaterial; // HUMANHEAD pdm + const idMaterial *dirDmgFrontMaterial; // HUMANHEAD pdm + const idMaterial *spiritMaterial; // HUMANHEAD cjr + idVec3 lastDamageLocation; // HUMANHEAD PDM: saved damage location + float hurtValue; // HUMANHEAD bjk: to smooth the health +}; + +//HUMANHEAD rww - render demo madness +#if _HH_RENDERDEMO_HACKS + void RENDER_DEMO_VIEWRENDER(const renderView_t *view, const hhPlayerView *pView = NULL); + void RENDER_DEMO_VIEWRENDER_END(void); +#else + #define RENDER_DEMO_VIEWRENDER(a, b)//ignore + #define RENDER_DEMO_VIEWRENDER_END() //ignore +#endif +//HUMANHEAD END + + +//------------------------------------------------------ +// hhPlayerView::SetViewOverlayMaterial +// +// HUMANHEAD: aob +//------------------------------------------------------ +ID_INLINE void hhPlayerView::SetViewOverlayMaterial( const idMaterial* material, int scratchBuffer ) { + viewOverlayMaterial = material; + + voTotalTime = -1; + voFinishTime = gameLocal.time + voTotalTime; + voRequiresScratchBuffer = scratchBuffer; +} + +//------------------------------------------------------ +// hhPlayerView::SetViewOverlayColor +// +// HUMANHEAD: cjr +//------------------------------------------------------ +ID_INLINE void hhPlayerView::SetViewOverlayColor( idVec4 color ) { + viewOverlayColor = color; +} + +//------------------------------------------------------ +// hhPlayerView::SetViewOverlayTime +// +// HUMANHEAD: cjr +// +// Time can be set in SetViewOverlayMaterial +// or set/updated with this function call +//------------------------------------------------------ +ID_INLINE void hhPlayerView::SetViewOverlayTime( int time, int scratchBuffer ) { + voTotalTime = time; + voFinishTime = gameLocal.time + voTotalTime; + voRequiresScratchBuffer = scratchBuffer; +} + +#endif /* __PREY_GAME_PLAYERVIEW_H__ */ + diff --git a/neo/prey/Prey/game_pod.cpp b/neo/prey/Prey/game_pod.cpp new file mode 100644 index 00000000..021bd820 --- /dev/null +++ b/neo/prey/Prey/game_pod.cpp @@ -0,0 +1,157 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhPod +// +// type of mine that can be spawned from a pod spawner +//========================================================================== + +CLASS_DECLARATION(hhMine, hhPod) +END_CLASS + +hhPod::hhPod() { + bMoverThink = false; + additionalAxis.Identity(); +} + +void hhPod::Spawn(void) { + // Uses clipModel because it is a moveable, which requires it + + // Turn off collision with corpses + int oldMask = GetPhysics()->GetClipMask(); + GetPhysics()->SetClipMask( oldMask & (~CONTENTS_CORPSE) ); + + // Rolling support + radius = (GetPhysics()->GetBounds()[1][0] - GetPhysics()->GetBounds()[0][0]) * 0.5f; + lastOrigin = GetPhysics()->GetOrigin(); + additionalAxis.Identity(); + BecomeActive(TH_THINK); +} + +void hhPod::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( additionalAxis ); + savefile->WriteBool( bMoverThink ); +} + +void hhPod::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( additionalAxis ); + savefile->ReadBool( bMoverThink ); +} + +bool hhPod::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + StartSound( "snd_pain", SND_CHANNEL_ANY ); + + float scale = 1.5f - (0.5f * health / spawnArgs.GetFloat("health")); + scale = idMath::ClampFloat(1.0f, 1.5f, scale); + SetShaderParm(SHADERPARM_ANY_DEFORM_PARM2, scale); + + return( idEntity::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhPod::Release() { + idVec3 initLinearVelocity, initAngularVelocity; + spawnArgs.GetVector( "init_velocity", "0 0 0", initLinearVelocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", initAngularVelocity ); + + // Make the pod physics kick in + physicsObj.EnableImpact(); + physicsObj.Activate(); + physicsObj.SetLinearVelocity( initLinearVelocity ); + physicsObj.SetAngularVelocity( initAngularVelocity ); + + // Start deforming again (disabled on pod spawners) + float parm1 = spawnArgs.GetFloat("deformParm1"); + float parm2 = spawnArgs.GetFloat("deformParm2"); + SetDeformation(DEFORMTYPE_POD, parm1, parm2); +} + + +bool hhPod::Collide( const trace_t &collision, const idVec3 &velocity ) { + AttemptToPlayBounceSound( collision, velocity ); + + return hhMine::Collide( collision, velocity ); +} + + +void hhPod::RollThink( void ) { + float movedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + + bool wasAtRest = IsAtRest(); + + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + curOrigin = GetPhysics()->GetOrigin(); + + dir = curOrigin - lastOrigin; + float movedDistanceSquared = dir.LengthSqr(); + + // if the pod moved + if ( movedDistanceSquared > 0.0f && movedDistanceSquared < 100.0f) { + + gravityNormal = GetPhysics()->GetGravityNormal(); + + // movement since last frame + movedDistance = idMath::Sqrt( movedDistanceSquared ); + dir *= 1.0f / movedDistance; + + // Get local coordinate axes + idVec3 right = -dir.Cross(gravityNormal); + + // Rotate about it proportional to the distance moved using axis/angle + angle = 180.0f * movedDistance / (radius*idMath::PI); + additionalAxis *= (idRotation( vec3_origin, right, angle).ToMat3()); + } + + // save state for next think + lastOrigin = curOrigin; + } + + Present(); +} + +void hhPod::Event_HoverTo( const idVec3 &position ) { + bMoverThink = true; + hhMine::Event_HoverTo( position ); +} + +void hhPod::Event_Unhover() { + bDetonateOnCollision = true; + bMoverThink = false; + hhMine::Event_Unhover(); +} + +void hhPod::Think() { + if ( bMoverThink ) { + hhMoveable::Think(); + } else { + RollThink(); + } +} + +void hhPod::ClientPredictionThink() { + if ( bMoverThink ) { + hhMoveable::Think(); + } else { + RollThink(); + } +} + +bool hhPod::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis * GetPhysics()->GetAxis().Inverse(); + return true; +} + diff --git a/neo/prey/Prey/game_pod.h b/neo/prey/Prey/game_pod.h new file mode 100644 index 00000000..b24075bf --- /dev/null +++ b/neo/prey/Prey/game_pod.h @@ -0,0 +1,34 @@ + +#ifndef __GAME_POD_H__ +#define __GAME_POD_H__ + +class hhPod : public hhMine { +public: + CLASS_PROTOTYPE( hhPod ); + + hhPod(); + 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 ); + void Release(); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Event_HoverTo( const idVec3 &position ); + virtual void Event_Unhover(); +protected: + void RollThink( void ); + +private: + float radius; // radius of barrel + idVec3 lastOrigin; // origin of the barrel the last frame + idMat3 additionalAxis; // transformation for visual model + bool bMoverThink; +}; + + +#endif /* __GAME_POD_H__ */ + diff --git a/neo/prey/Prey/game_podspawner.cpp b/neo/prey/Prey/game_podspawner.cpp new file mode 100644 index 00000000..2b2c6329 --- /dev/null +++ b/neo/prey/Prey/game_podspawner.cpp @@ -0,0 +1,141 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhPodSpawner +// +// type of mine spawner that animates and spawns pods +//========================================================================== + +const idEventDef EV_DropPod("", NULL); +const idEventDef EV_DoneSpawning("", NULL); + +CLASS_DECLARATION(hhMineSpawner, hhPodSpawner) + EVENT( EV_DropPod, hhPodSpawner::Event_DropPod) + EVENT( EV_PlayIdle, hhPodSpawner::Event_PlayIdle) + EVENT( EV_DoneSpawning, hhPodSpawner::Event_DoneSpawning) +END_CLASS + + +void hhPodSpawner::Spawn(void) { + fl.takedamage = false; + GetPhysics()->SetContents( CONTENTS_BODY ); + + pod = NULL; + spawning = false; + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + spawnAnim = GetAnimator()->GetAnim("spawn"); + + PostEventMS(&EV_PlayIdle, 0); +} + +void hhPodSpawner::Save(idSaveGame *savefile) const { + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( spawnAnim ); + savefile->WriteBool( spawning ); + savefile->WriteObject(pod); +} + +void hhPodSpawner::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( spawnAnim ); + savefile->ReadBool( spawning ); + savefile->ReadObject( reinterpret_cast(pod) ); +} + +void hhPodSpawner::SpawnMine() { + idVec3 offset; + + if (spawning || health < 0 || fl.isDormant) { + return; + } + + population++; + spawning = true; + + offset = idVec3(0, 0, -64); // Move up to top + + idDict args; + args.Clear(); + args.Set( "origin", (GetPhysics()->GetOrigin() + offset).ToString() ); + args.Set( "nodrop", "1" ); // Don't put on the floor, wait for release + args.Set( "deformType", "0" ); // Don't start deforming until released + + // pass along any spawn keys + const idKeyValue *arg = spawnArgs.MatchPrefix("spawn_"); + while( arg ) { + args.Set( arg->GetKey().Right( arg->GetKey().Length() - 6 ), arg->GetValue() ); + arg = spawnArgs.MatchPrefix( "spawn_", arg ); + } + + // spawn a pod + pod = static_cast(gameLocal.SpawnObject(spawnArgs.GetString("def_pod"), &args)); + + // attach to bone + const char *podBone = "PodPosition"; + pod->MoveToJoint(this, podBone); + pod->AlignToJoint(this, podBone); + pod->BindToJoint(this, podBone, true); + pod->fl.takedamage = false; // No damage while attached + pod->SetSpawner(this); + + // Due to the animation, bone isn't in position yet, so wait a little + // before making it visible + pod->Hide(); + pod->PostEventMS(&EV_Show, 500); + + // start animation + if (spawnAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, spawnAnim, gameLocal.time, 500); + int opentime = GetAnimator()->GetAnim( spawnAnim )->Length(); + PostEventMS( &EV_PlayIdle, opentime ); + + //TODO: Move this to a frame command (event) + PostEventMS( &EV_DropPod, 3700 ); + + PostEventMS( &EV_DoneSpawning, opentime ); + + StartSound( "snd_spawn", SND_CHANNEL_ANY ); + } +} + +void hhPodSpawner::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // Don't actually take damage, but give feedback + + // Play pain + if (painAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 500); + int opentime = GetAnimator()->GetAnim( painAnim )->Length(); + PostEventMS( &EV_PlayIdle, opentime ); + StartSound( "snd_pain", SND_CHANNEL_ANY ); + } +} + +void hhPodSpawner::Event_PlayIdle() { + if (idleAnim) { + GetAnimator()->ClearAllAnims(gameLocal.time, 0); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 0); + } +} + +void hhPodSpawner::Event_DropPod() { + if (pod) { + pod->Unbind(); + pod->fl.takedamage = true; + pod->Release(); + } +} + +void hhPodSpawner::Event_DoneSpawning() { + spawning = false; + CheckPopulation(); +} + diff --git a/neo/prey/Prey/game_podspawner.h b/neo/prey/Prey/game_podspawner.h new file mode 100644 index 00000000..349ac98a --- /dev/null +++ b/neo/prey/Prey/game_podspawner.h @@ -0,0 +1,31 @@ + +#ifndef __GAME_PODSPAWNER_H__ +#define __GAME_PODSPAWNER_H__ + +class hhPodSpawner : public hhMineSpawner { +public: + CLASS_PROTOTYPE( hhPodSpawner ); + + void Spawn( void ); + 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 ); + void SpawnMine(); + +protected: + void Event_PlayIdle(); + void Event_DropPod(); + void Event_DoneSpawning(); + +private: + int idleAnim; + int painAnim; + int spawnAnim; + + bool spawning; + hhPod *pod; +}; + + +#endif /* __GAME_PODSPAWNER_H__ */ diff --git a/neo/prey/Prey/game_poker.cpp b/neo/prey/Prey/game_poker.cpp new file mode 100644 index 00000000..e8a5b166 --- /dev/null +++ b/neo/prey/Prey/game_poker.cpp @@ -0,0 +1,807 @@ +// game_poker.cpp +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// Static declaration of our hands array +const hand_t localhands[NUM_POKER_HANDS] = { + { HAND_ROYALFLUSH, 5000, PREQ_ROYALS|PREQ_FLUSH|PREQ_STRAIGHT }, + { HAND_STRAIGHTFLUSH, 1000, PREQ_FLUSH|PREQ_STRAIGHT }, + { HAND_FOUROFKIND, 100, PREQ_4MATCH }, + { HAND_FULLHOUSE, 50, PREQ_FULLHOUSE }, + { HAND_FLUSH, 20, PREQ_FLUSH }, + { HAND_STRAIGHT, 10, PREQ_STRAIGHT }, + { HAND_THREEOFKIND, 5, PREQ_3MATCH }, + { HAND_TWOPAIR, 3, PREQ_2PAIR }, + // Jacks or better is hardcoded at 2 + { HAND_PAIR, 1, PREQ_2MATCH }, + { HAND_NOTHING, 0, 0 } +}; + +// Assign reference to our local array to our static class +const hand_t * hhPoker::hands = localhands; + + +//============================================================== +// hhPokerHand utility class +//============================================================== + +CLASS_DECLARATION(idClass, hhPokerHand) +END_CLASS + +hhPokerHand::hhPokerHand() { + Clear(); +} + +void hhPokerHand::Save(idSaveGame *savefile) const { + savefile->Write(values, sizeof(suitHist_t)*NUM_SUITS); + savefile->Write(suits, sizeof(valueHist_t)*NUM_CARD_VALUES); + + savefile->WriteInt(cards.Num()); // idList + for (int i=0; iWrite(&cards[i], sizeof(hhCard)); + } +} + +void hhPokerHand::Restore( idRestoreGame *savefile ) { + int i, num; + hhCard card; + + savefile->Read(values, sizeof(suitHist_t)*NUM_SUITS); + savefile->Read(suits, sizeof(valueHist_t)*NUM_CARD_VALUES); + + cards.Clear(); + savefile->ReadInt( num ); + cards.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->Read(&card, sizeof(hhCard)); + cards[i] = card; + } +} + +void hhPokerHand::operator=(hhPokerHand &other) { + Clear(); + for (int ix=0; ix maxcount) { + maxcount = values[ix].count; + } + } + return maxcount; +} + +int hhPokerHand::ValueOfMaxCount() { + int maxcount = 0; + int maxcountvalue = -1; + for (int ix=0; ix maxcount) { + maxcount = values[ix].count; + maxcountvalue = ix; + } + } + return maxcountvalue; +} + +int hhPokerHand::GetLowValue() { + int first = -1; + for (int ix=0; ix 0) { + first = ix; + break; + } + } + return first; +} + +int hhPokerHand::GetHighValue() { + int last = -1; + for (int ix=0; ix 0) { + last = ix; + } + } + return last; +} + +bool hhPokerHand::HasValue(int value) { + return values[value].count > 0; +} + +int hhPokerHand::NumUniqueValues() { + int numvalues = 0; + for (int ix=0; ix 0) { + numvalues++; + } + } + return numvalues; +} + +int hhPokerHand::NumOfValue(int value) { + assert(value>=0 && value 0) { + count++; + } + } + return count; +} + +int hhPokerHand::SuitOfMaxCount() { + int maxcount = 0; + int maxsuit = -1; + for (int ix=0; ix maxcount) { + maxcount = suits[ix].count; + maxsuit = ix; + } + } + return maxsuit; +} + +bool hhPokerHand::Search(hhCard &card) { + for (int ix=0; ix", NULL); + +CLASS_DECLARATION(hhConsole, hhPoker) + EVENT( EV_Deal, hhPoker::Event_Deal) + EVENT( EV_Draw, hhPoker::Event_Draw) + EVENT( EV_UpdateView, hhPoker::Event_UpdateView) +END_CLASS + +void hhPoker::Spawn() { + + Reset(); +} + +void hhPoker::Reset() { + bCanDeal = true; + bCanIncBet = true; + bCanDecBet = true; + bCanDraw = false; + bGameOver = false; + Bet = PlayerBet = 1; + memset(markedCards, 0, sizeof(markedCards)); + victoryAmount = spawnArgs.GetInt("victory"); + PlayerCredits = spawnArgs.GetInt("credits"); + currentHandIndex = -1; + creditsWon = 0; + + bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + PlayerHand.Clear(); + + UpdateView(); +} + +void hhPoker::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject(deck); + savefile->WriteStaticObject(PlayerHand); + savefile->Write(markedCards, sizeof(bool)*5); + savefile->WriteInt(Bet); + savefile->WriteInt(PlayerBet); + savefile->WriteInt(PlayerCredits); + savefile->WriteInt(victoryAmount); + savefile->WriteInt(currentHandIndex); + + savefile->WriteBool( bGameOver ); + savefile->WriteBool( bCanDeal ); + savefile->WriteBool( bCanIncBet ); + savefile->WriteBool( bCanDecBet ); + savefile->WriteBool( bCanDraw ); + savefile->WriteBool( bCanMark1 ); + savefile->WriteBool( bCanMark2 ); + savefile->WriteBool( bCanMark3 ); + savefile->WriteBool( bCanMark4 ); + savefile->WriteBool( bCanMark5 ); + savefile->WriteInt( creditsWon ); +} + +void hhPoker::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject(deck); + savefile->ReadStaticObject(PlayerHand); + savefile->Read(markedCards, sizeof(bool)*5); + savefile->ReadInt(Bet); + savefile->ReadInt(PlayerBet); + savefile->ReadInt(PlayerCredits); + savefile->ReadInt(victoryAmount); + savefile->ReadInt(currentHandIndex); + + savefile->ReadBool( bGameOver ); + savefile->ReadBool( bCanDeal ); + savefile->ReadBool( bCanIncBet ); + savefile->ReadBool( bCanDecBet ); + savefile->ReadBool( bCanDraw ); + savefile->ReadBool( bCanMark1 ); + savefile->ReadBool( bCanMark2 ); + savefile->ReadBool( bCanMark3 ); + savefile->ReadBool( bCanMark4 ); + savefile->ReadBool( bCanMark5 ); + savefile->ReadInt( creditsWon ); +} + +void hhPoker::Deal() { + + // Shuffle + deck.Generate(); + deck.Shuffle(); + + Bet = PlayerBet; + PlayerCredits -= Bet; + creditsWon = 0; + + // Deal initial hand + PlayerHand.Clear(); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + PlayerHand.AddCard(deck.GetCard()); + + EvaluateHand(false); + + bCanDeal = bCanIncBet = bCanDecBet = false; + bCanDraw = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = true; + for (int ix=0; ix<5; ix++) { + markedCards[ix] = true; + } + + UpdateView(); +} + +void hhPoker::Draw() { + bCanDeal = bCanIncBet = bCanDecBet = true; + bCanDraw = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + + float luck = idMath::ClampFloat(0.0f, 1.0f, spawnArgs.GetFloat("luck")); + if (luck) { + BestHand(luck); + } + else { + // Replace marked cards + for (int ix=0; ix<5; ix++) { + if (markedCards[ix]) { + PlayerHand.cards[ix] = deck.GetCard(); + markedCards[ix] = false; + } + } + hhPokerHand temp; + temp = PlayerHand; + PlayerHand = temp; // Rebuild player hand for correct stats + } + + EvaluateHand(true); + UpdateView(); +} + +void hhPoker::Mark(int card) { + markedCards[card] ^= 1; + UpdateView(); +} + +void hhPoker::IncBet() { + + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanIncBet) { + int oldBet = PlayerBet; + PlayerBet = idMath::ClampInt(PlayerBet, PlayerCredits, PlayerBet+amount); + PlayerBet = idMath::ClampInt(0, 999999, PlayerBet); + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + +void hhPoker::DecBet() { + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanDecBet) { + int oldBet = PlayerBet; + if (PlayerBet > amount) { + PlayerBet -= amount; + } + else if (PlayerBet > 1) { + PlayerBet = 1; + } + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + + +/* + hhPoker utility functions +*/ + +void hhPoker::EvaluateHand(bool score) { + int ix; + currentHandIndex = -1; + + int requirements = 0; + int low = PlayerHand.GetLowValue(); + int high = PlayerHand.GetHighValue(); + int maxcount = PlayerHand.MaxCountOfValues(); + + if (low >= CARD_TEN) { + requirements |= PREQ_ROYALS; + } + + if (PlayerHand.NumUniqueSuits() == 1) { + requirements |= PREQ_FLUSH; + } + + // Check for straight + if (maxcount==1) { + requirements |= PREQ_STRAIGHT; + int cur = low; + for (ix=1; ix<5; ix++) { + int next = cur+1; + if (low == CARD_DEUCE && next == CARD_SIX && PlayerHand.HasValue(CARD_ACE)) { + // If looking for a '6', accept an 'Ace' also for Ace low straights + } + else if (!PlayerHand.HasValue(next)) { + requirements &= ~PREQ_STRAIGHT; + break; + } + cur = next; + } + } + + // Check for Pairs, etc. + for (ix=0; ix= CARD_JACK) { + payoff = 2; + } + + creditsWon = Bet * payoff; + PlayerCredits += creditsWon; + PlayerCredits = idMath::ClampInt(0, 999999999, PlayerCredits); + if (PlayerCredits < PlayerBet) { + PlayerBet = PlayerCredits; + } + + if (PlayerCredits <= 0) { + bCanIncBet = bCanDecBet = bCanDraw = bCanDeal = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + bGameOver = true; + } + + if (victoryAmount && PlayerCredits >= victoryAmount) { + StartSound( "snd_victory", SND_CHANNEL_ANY ); + ActivateTargets( gameLocal.GetLocalPlayer() ); + victoryAmount = 0; + } + else if (payoff > 10) { + StartSound( "snd_winbig", SND_CHANNEL_ANY ); + } + else if (payoff > 0) { + StartSound( "snd_win", SND_CHANNEL_ANY ); + } + else { + StartSound( "snd_lose", SND_CHANNEL_ANY ); + } + } +} + +void hhPoker::UpdateView() { + // UpdateView() is posted as an event because sometimes we're already nested down in the gui handling code when it is called + // and it in turn re-enters the gui handling code with HandleNamedEvent() + CancelEvents(&EV_UpdateView); + PostEventMS(&EV_UpdateView, 0); +} + +void hhPoker::Event_UpdateView() { + int ix; + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + + bool atLeastOneCardKept = !(markedCards[0] && markedCards[1] && markedCards[2] && markedCards[3] && markedCards[4]); + + gui->SetStateBool("bgameover", bGameOver); + gui->SetStateBool("bcanincbet", bCanIncBet); + gui->SetStateBool("bcandecbet", bCanDecBet); + gui->SetStateBool("bcandraw", bCanDraw);// && atLeastOneCardKept); + gui->SetStateBool("bcandeal", bCanDeal); + gui->SetStateBool("bcanmark1", bCanMark1); + gui->SetStateBool("bcanmark2", bCanMark2); + gui->SetStateBool("bcanmark3", bCanMark3); + gui->SetStateBool("bcanmark4", bCanMark4); + gui->SetStateBool("bcanmark5", bCanMark5); + gui->SetStateInt("currentbet", PlayerBet); + gui->SetStateInt("credits", PlayerCredits); + gui->SetStateInt("hand", currentHandIndex); + gui->SetStateInt("creditswon", creditsWon); + + for (ix=0; ix<5; ix++) { + gui->SetStateInt(va("Player%d_Visible", ix+1), 0); + } + + // Show player hand + for (ix=0; ixSetStateInt(va("Player%d_Visible", ix+1), 1); + gui->SetStateInt(va("Player%d_Suit", ix+1), PlayerHand.cards[ix].Suit()); + char cardChar = PlayerHand.cards[ix].ValueName(); + if (cardChar == 'T') { + gui->SetStateString(va("Player%d_Card", ix+1), "10"); + } + else { + gui->SetStateString(va("Player%d_Card", ix+1), va("%c", cardChar)); + } + gui->SetStateInt(va("Player%d_Red", ix+1), PlayerHand.cards[ix].Suit()==SUIT_HEARTS || PlayerHand.cards[ix].Suit()==SUIT_DIAMONDS ? 1 : 0); + gui->SetStateString(va("Player%d_Mark", ix+1), markedCards[ix] ? "X" : ""); + gui->SetStateBool(va("Player%d_Marked", ix+1), markedCards[ix] ); + } + + gui->StateChanged(gameLocal.time, true); + CallNamedEvent("Update"); + } +} + +bool hhPoker::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("deal") == 0) { + Deal(); + } + else if (token.Icmp("draw") == 0) { + Draw(); + } + else if (token.Icmp("mark1") == 0) { + Mark(0); + } + else if (token.Icmp("mark2") == 0) { + Mark(1); + } + else if (token.Icmp("mark3") == 0) { + Mark(2); + } + else if (token.Icmp("mark4") == 0) { + Mark(3); + } + else if (token.Icmp("mark5") == 0) { + Mark(4); + } + else if (token.Icmp("incbet") == 0) { + IncBet(); + } + else if (token.Icmp("decbet") == 0) { + DecBet(); + } + else if (token.Icmp("reset") == 0) { + Reset(); + } + else if (token.Icmp("restart") == 0) { + bCanDeal = bCanIncBet = bCanDecBet = true; + bCanDraw = bCanMark1 = bCanMark2 = bCanMark3 = bCanMark4 = bCanMark5 = false; + PlayerCredits = spawnArgs.GetInt("credits"); + bGameOver = false; + Bet = PlayerBet = 1; + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhPoker::Event_Deal() { + Deal(); +} + +void hhPoker::Event_Draw() { + Draw(); +} + +// Experiment: get best hand given discards +void hhPoker::BestHand(float probability) { + int ix; + + hhPokerHand hand; + hhPokerHand discards; + hhPokerHand originalHand; + + hand.Clear(); + originalHand.Clear(); + discards.Clear(); + + for (ix=0; ix<5; ix++) { + if (markedCards[ix]) { + discards.AddCard(PlayerHand.cards[ix]); + } + else { + hand.AddCard(PlayerHand.cards[ix]); + originalHand.AddCard(PlayerHand.cards[ix]); + } + } + + int possibilities = 0xFF; + + // Check for possibilities of requirements using hands + for (ix=0; ix 1) { + possibilities &= ~PREQ_STRAIGHT; + } + if (first != -1 && last != -1 && last-first>=5) { + if (hand.HasValue(CARD_ACE)) { // Extra check to allow for ace low straight + last = first; + first = -1; // put ace at begining, find new last and try again + for (ix=0; ix 0) { + last = ix; + } + } + if (last-first>=5) { + possibilities &= ~PREQ_STRAIGHT; + } + } + else { + possibilities &= ~PREQ_STRAIGHT; + } + } + + // Check for full house possibility + if (hand.NumUniqueValues() > 2) { + possibilities &= ~PREQ_FULLHOUSE; + possibilities &= ~PREQ_2PAIR; + } + + // check for 'x of a kind' possibility + if (maxcount + discards.cards.Num() < 4) { + possibilities &= ~PREQ_4MATCH; + } + if (maxcount + discards.cards.Num() < 3) { + possibilities &= ~PREQ_3MATCH; + } + if (maxcount + discards.cards.Num() < 2) { + possibilities &= ~PREQ_2MATCH; + } + + // Go through hands in decending order of greatness looking for possibilities + for (ix=0; ixCARD_TEN ? CARD_TEN : first; // Start at ten or lower so it will fit + } + while (hand.HasValue(value)) { + value++; + } + } + + int hotvalue; + if (requirements&PREQ_FULLHOUSE) { + hotvalue = hand.ValueOfMaxCount(); + if (hotvalue != -1) { + if (hand.NumOfValue(hotvalue) < 3) { + value = hotvalue; + } + else { + value = (hotvalue==first) ? last : first; + } + } + } + + if (requirements&PREQ_2PAIR) { + if (hand.NumOfValue(first) < 2) { + value = first; + } + else if (hand.NumOfValue(last) < 2) { + value = last; + } + else { + value = CARD_ACE; + while (hand.HasValue(value)) { + value--; + } + } + } + + if ((hand.MaxCountOfValues()<4) && (requirements&PREQ_4MATCH)) { + value = hand.ValueOfMaxCount(); + } + if ((hand.MaxCountOfValues()<3) && (requirements&PREQ_3MATCH)) { + value = hand.ValueOfMaxCount(); + } + if ((hand.MaxCountOfValues()<2) && (requirements&PREQ_2MATCH)) { + value = hand.ValueOfMaxCount(); + } + + // Determine suit properties + if (requirements&PREQ_FLUSH) { + suit = hand.SuitOfMaxCount(); + if (suit == -1) { + suit = gameLocal.random.RandomInt() % NUM_SUITS; // random fixed suit + } + if (value == -1) { // Need a value + value = CARD_ACE; + while (!deck.HasCard(hhCard(value, suit))) { + value--; + } + } + } + else if (value != -1) { + // Choose a suit different from held values + suit = 0; + while ( !deck.HasCard(hhCard(value, suit)) ) { + if (++suit >= NUM_SUITS) { + break; + } + } + if (suit >= NUM_SUITS) { // Can't make our hand - set back to a used card and it will break out to next possibility below + suit = SUIT_DIAMONDS; + } + } + + // Add card to hand + if (value == -1) { // This means any card is okay, give one from deck + hand.AddCard(deck.GetCard()); + } + else if (deck.HasCard(hhCard(value,suit))) { + hand.AddCard(deck.GetCard(value, suit)); + } + else { + for (int jx=originalHand.cards.Num(); jx probability) { + hand.cards[ix] = deck.GetCard(); + bHandCompromised = true; // only compromise hand with one card + } + } + + // Copy into playerhand + hhPokerHand temp; + int drawcard = originalHand.cards.Num(); + for (ix=0; ix<5; ix++) { + if (markedCards[ix]) { + PlayerHand.cards[ix] = hand.cards[drawcard++]; + markedCards[ix] = false; + } + temp.AddCard(PlayerHand.cards[ix]); + } + PlayerHand.Clear(); // Rebuild player hand for correct stats + for (ix=0; ix<5; ix++) { + PlayerHand.AddCard(temp.cards[ix]); + } +} + diff --git a/neo/prey/Prey/game_poker.h b/neo/prey/Prey/game_poker.h new file mode 100644 index 00000000..1e54c77f --- /dev/null +++ b/neo/prey/Prey/game_poker.h @@ -0,0 +1,123 @@ + +#ifndef __GAME_POKER_H__ +#define __GAME_POKER_H__ + +extern const idEventDef EV_UpdateView; + +#define PREQ_FLUSH 0x00000001 +#define PREQ_STRAIGHT 0x00000002 +#define PREQ_ROYALS 0x00000004 +#define PREQ_4MATCH 0x00000008 +#define PREQ_3MATCH 0x00000010 +#define PREQ_2MATCH 0x00000020 +#define PREQ_FULLHOUSE 0x00000040 +#define PREQ_2PAIR 0x00000080 + +typedef enum pokerhand_e { + HAND_ROYALFLUSH=0, + HAND_STRAIGHTFLUSH, + HAND_FOUROFKIND, + HAND_FULLHOUSE, + HAND_FLUSH, + HAND_STRAIGHT, + HAND_THREEOFKIND, + HAND_TWOPAIR, + HAND_PAIR, + HAND_NOTHING, + NUM_POKER_HANDS +}pokerhand_t; + +typedef struct hand_s { + pokerhand_t hand; + int payoff; + int requirement; +} hand_t; + + +class hhPokerHand : public idClass { + CLASS_PROTOTYPE( hhPokerHand ); + +public: + typedef struct valueHist_s { + int count; + } valueHist_t; + + typedef struct suitHist_s { + int count; + } suitHist_t; + + hhPokerHand(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Clear(); + int MaxCountOfValues(); + int ValueOfMaxCount(); + int GetLowValue(); + int GetHighValue(); + bool HasValue(int value); + int NumOfValue(int value); + int SuitOfMaxCount(); + int NumUniqueValues(); + int NumUniqueSuits(); + void AddCard(hhCard card); + bool Search(hhCard &card); + void operator=(hhPokerHand &other); + +public: + suitHist_t suits[NUM_SUITS]; + valueHist_t values[NUM_CARD_VALUES]; + idList cards; + +}; + +class hhPoker : public hhConsole { + CLASS_PROTOTYPE( hhPoker ); +public: + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Deal(); + void Draw(); + void Mark(int card); + void IncBet(); + void DecBet(); + void BestHand(float probability); + void Reset(); + + void EvaluateHand(bool score); + void UpdateView(); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + void Event_Deal(); + void Event_Draw(); + void Event_UpdateView(); + +private: + static const hand_t *hands; + hhDeck deck; + hhPokerHand PlayerHand; + + bool markedCards[5]; + int Bet; + int PlayerBet; + int PlayerCredits; + int victoryAmount; + int currentHandIndex; + int creditsWon; + + bool bGameOver; + bool bCanDeal; + bool bCanIncBet; + bool bCanDecBet; + bool bCanDraw; + bool bCanMark1; + bool bCanMark2; + bool bCanMark3; + bool bCanMark4; + bool bCanMark5; +}; + +#endif /* __GAME_POKER_H__ */ diff --git a/neo/prey/Prey/game_portal.cpp b/neo/prey/Prey/game_portal.cpp new file mode 100644 index 00000000..6e42ddda --- /dev/null +++ b/neo/prey/Prey/game_portal.cpp @@ -0,0 +1,1168 @@ +//************************************************************************** +//** +//** GAME_PORTAL.CPP +//** +//** Game code for Prey-specific portals +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define MP_PORTAL_RANGE_DEFAULT 356.0f //rww - was 512, and then it was 256, and now it is 356 +#define MAX_PORTAL_BOUNDS 256.0f //used for unchanging bounds in mp + +//========================================================================== +// +// hhArtificialPortal +// +//========================================================================== + +const idEventDef EV_SetGamePortalState("setPortalState", "dd"); + +#if GAMEPORTAL_PVS + +CLASS_DECLARATION(idEntity, hhArtificialPortal) + EVENT( EV_SetGamePortalState, hhArtificialPortal::Event_SetPortalState ) +END_CLASS + +void hhArtificialPortal::Spawn() { + areaPortal = gameRenderWorld->FindGamePortal( GetName() ); + if (areaPortal && !gameLocal.isClient) { + SetPortalState(spawnArgs.GetBool("startOpenPVS"), spawnArgs.GetBool("startOpenSound")); + } + fl.networkSync = true; +} + +void hhArtificialPortal::SetPortalState(bool openPVS, bool openSound) { + if (areaPortal && !gameLocal.isClient) { + int blockMask = (openPVS ? PS_BLOCK_NONE : PS_BLOCK_VIEW) | (openSound ? PS_BLOCK_NONE : PS_BLOCK_SOUND); + gameLocal.SetPortalState( areaPortal, blockMask ); + } +} + +void hhArtificialPortal::Event_SetPortalState(bool openPVS, bool openSound) { + SetPortalState(openPVS, openSound); +} + +void hhArtificialPortal::Save(idSaveGame *savefile) const { + savefile->WriteInt(areaPortal); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } +} + +void hhArtificialPortal::Restore(idRestoreGame *savefile) { + savefile->ReadInt(areaPortal); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } +} + + +#endif + + +//========================================================================== +// +// hhPortal +// +//========================================================================== + +const idEventDef EV_Opened("", NULL); +const idEventDef EV_Closed("", NULL); +const idEventDef EV_PortalSpark("", NULL ); +const idEventDef EV_PortalSparkEnd("", NULL ); +const idEventDef EV_ShowGlowPortal( "showGlowPortal", NULL ); +const idEventDef EV_HideGlowPortal( "hideGlowPortal", NULL ); + +CLASS_DECLARATION(hhAnimatedEntity, hhPortal) + EVENT( EV_PostSpawn, hhPortal::PostSpawn ) + EVENT( EV_Activate, hhPortal::Event_Trigger) + EVENT( EV_Opened, hhPortal::Event_Opened ) + EVENT( EV_Closed, hhPortal::Event_Closed ) + EVENT( EV_ResetGravity, hhPortal::Event_ResetGravity ) + EVENT( EV_PortalSpark, hhPortal::Event_PortalSpark ) + EVENT( EV_PortalSparkEnd, hhPortal::Event_PortalSparkEnd ) + EVENT( EV_ShowGlowPortal, hhPortal::Event_ShowGlowPortal ) + EVENT( EV_HideGlowPortal, hhPortal::Event_HideGlowPortal ) +END_CLASS + +hhPortal::hhPortal(void) { +#if GAMEPORTAL_PVS + areaPortal = 0; +#endif +} + +hhPortal::~hhPortal() { + proximityEntities.Clear(); // Clear the list of potential entities to be portalled + SAFE_REMOVE(m_portalIdleFx); + +#if GAMEPORTAL_PVS + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + } +#endif +} + +void hhPortal::Spawn(void) { + idBounds bounds; + + fl.clientEvents = true; + + bNoTeleport = spawnArgs.GetBool( "noTeleport"); + bGlowPortal = spawnArgs.GetBool( "glowPortal"); // This is a glow portal, and so will have fx, sound, particles, etc + closeDelay = spawnArgs.GetFloat( "closeDelay"); + monsterportal = spawnArgs.GetBool( "monsterportal" ); + distanceToggle = spawnArgs.GetFloat( "distanceToggle" ); + if (gameLocal.isMultiplayer && bGlowPortal/* && distanceToggle == 0.0f*/) { //mp has a default shutoff distance for glow portals. + //now ignoring distance toggle keys in mp, and forcing it. + distanceToggle = MP_PORTAL_RANGE_DEFAULT; + } + if (!spawnArgs.GetFloat( "distanceCull", "0.0", distanceCull )) { //rww - distance to shut off the vis gameportal but not the render portal + //if not value specified, default to shaderParm5+4 + if (!renderEntity.shaderParms[5]) { + distanceCull = 0.0f; + } + else { + distanceCull = renderEntity.shaderParms[5]+4.0f; + } + } + areaPortalCulling = false; + if ( !gameLocal.isMultiplayer && bGlowPortal ) { + alertMonsters = spawnArgs.GetBool( "alertMonsters", "0" ); + } else { + alertMonsters = false; + } + + if ( bNoTeleport ) { + GetPhysics()->SetContents( 0 ); + } else { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + + // Setup the initial state for the portal + if(spawnArgs.GetBool("startActive")) { // Start Active + portalState = PORTAL_OPENED; + + if ( bGlowPortal ) { + int anim = GetAnimator()->GetAnim("opened"); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + StartSound( "snd_loop", SND_CHANNEL_ANY ); + PostEventSec( &EV_PortalSpark, gameLocal.random.RandomFloat() ); + SetSkinByName( spawnArgs.GetString( "skin" ) ); + } + } else { // Start closed + Hide(); + portalState = PORTAL_CLOSED; + GetPhysics()->SetContents( 0 ); //rww - if closed, ensure things will not hit and go through + } + +#if GAMEPORTAL_PVS + const char *gamePortalName = spawnArgs.GetString("gamePortalName", GetName()); + areaPortal = gameRenderWorld->FindGamePortal( gamePortalName ); + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, portalState == PORTAL_CLOSED ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +#endif + + // Default to normal gravity. This could be changed by any zones the portal is within + SetGravity( gameLocal.GetGravity() ); + + BecomeActive( TH_THINK | TH_UPDATEVISUALS | TH_ANIMATE ); + + UpdateVisuals(); + + PostEventMS( &EV_PostSpawn, 0 ); + + fl.networkSync = true; //rww + + if (gameLocal.isMultiplayer) { //rww - if portals grab their renderEntity bounds from the animation, they can cause client-server pvs discrepancies + renderEntity.bounds[0] = idVec3(-MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS); + renderEntity.bounds[1] = idVec3(MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS); + } + + proximityEntities.Clear(); // Clear the list of potential entities to be portalled +} + + +void hhPortal::PostSpawn( void ) { + CheckForBuddy(); +} + +void hhPortal::Save(idSaveGame *savefile) const { +#if GAMEPORTAL_PVS + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } +#endif + savefile->WriteInt( portalState ); + savefile->WriteBool( bNoTeleport ); + savefile->WriteBool( bGlowPortal ); + savefile->WriteVec3( portalGravity ); + savefile->WriteFloat( closeDelay ); + savefile->WriteBool( monsterportal ); + savefile->WriteBool( alertMonsters ); + + savefile->WriteInt( slavePortals.Num() ); // idList > + for (int i=0; iWriteFloat(distanceToggle); + savefile->WriteFloat(distanceCull); + savefile->WriteBool(areaPortalCulling); + + savefile->WriteInt( proximityEntities.Num() ); + for( int i = 0; i < proximityEntities.Num(); i++ ) { + proximityEntities[i].entity.Save( savefile ); + savefile->WriteVec3( proximityEntities[i].lastPortalPoint ); + } + + m_portalIdleFx.Save(savefile); +} + +void hhPortal::Restore( idRestoreGame *savefile ) { + int i, num; + +#if GAMEPORTAL_PVS + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } +#endif + savefile->ReadInt( (int &)portalState ); + savefile->ReadBool( bNoTeleport ); + savefile->ReadBool( bGlowPortal ); + savefile->ReadVec3( portalGravity ); + savefile->ReadFloat( closeDelay ); + savefile->ReadBool( monsterportal ); + savefile->ReadBool( alertMonsters ); + + slavePortals.Clear(); + savefile->ReadInt( num ); // idList > + slavePortals.SetNum( num ); + for (i=0; iReadFloat(distanceToggle); + savefile->ReadFloat(distanceCull); + savefile->ReadBool(areaPortalCulling); + + savefile->ReadInt( num ); + proximityEntities.SetNum( num ); + for( i = 0; i < num; i++ ) { + proximityEntities[i].entity.Restore( savefile ); + savefile->ReadVec3( proximityEntities[i].lastPortalPoint ); + } + + m_portalIdleFx.Restore(savefile); +} + +void hhPortal::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat(portalGravity.x); + msg.WriteFloat(portalGravity.y); + msg.WriteFloat(portalGravity.z); + msg.WriteBits(masterPortal.GetSpawnId(), 32); + + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]); + msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]); + + msg.WriteBits(portalState, 4); +} + +void hhPortal::ReadFromSnapshot( const idBitMsgDelta &msg ) { + portalGravity.x = msg.ReadFloat(); + portalGravity.y = msg.ReadFloat(); + portalGravity.z = msg.ReadFloat(); + masterPortal.SetSpawnId(msg.ReadBits(32)); + + renderEntity.shaderParms[SHADERPARM_MODE] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadFloat(); + + portalStates_t newPortalState = (portalStates_t)msg.ReadBits(4); + if ((newPortalState == PORTAL_CLOSED || newPortalState == PORTAL_OPENED) && + newPortalState != portalState && portalState != PORTAL_CLOSING && portalState != PORTAL_OPENING) { + Trigger(this); + } +} + +void hhPortal::ClientPredictionThink( void ) { + Think(); +} + +void hhPortal::CheckPlayerDistances(void) { + float closest = distanceToggle+distanceCull; + hhPortal *targetPortal = NULL; + + if (cameraTarget && cameraTarget->IsType(hhPortal::Type)) { //we want to measure distance from the target portal too if we have one + targetPortal = static_cast(cameraTarget); + } + + for (int i = 0; i < gameLocal.numClients; i++) { //loop through any active clients and get the closest distance to one. + idEntity *ent = gameLocal.entities[i]; + if (ent && ent->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(ent); + + if (pl->health > 0 && !pl->spectating && !pl->InVehicle()) { //don't open for spectators or dead players or players in vehicles + float d = (pl->GetOrigin()-GetOrigin()).Length(); + if (d < closest) { + closest = d; + } + if (targetPortal) { //check distance from the target portal + d = (pl->GetOrigin()-targetPortal->GetOrigin()).Length(); + if (d < closest) { + closest = d; + } + } + } + } + } + + if (distanceToggle != 0.0f) { //toggling portal based on distance of player + if (closest < distanceToggle) { //should be open + if (portalState == PORTAL_CLOSED) { + Trigger(this); + } + } + else { //should be closed + if (portalState == PORTAL_OPENED) { + Trigger(this); + } + } + } +#if GAMEPORTAL_PVS // jmarshall - added + if (distanceCull != 0.0f) { //toggling only area portal based on distance of player + if (closest >= distanceCull) { //should be closed + if (!areaPortalCulling) { + areaPortalCulling = true; + if (!gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + } + } + } + else if (areaPortalCulling && (portalState == PORTAL_OPENING || portalState == PORTAL_OPENED)) { //otherwise make sure it's on (if the portal is open) + if (!gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } + areaPortalCulling = false; + } + } +# endif +} + + +//========================================================================== +// +// hhPortal::Think +// +//========================================================================== + +#define NEAR_CLIP 0 //6.5 + +void hhPortal::Think( void ) { + int i; + idEntity *hit; + idPlane plane; + + // jmarshall - portal. + if ((distanceCull != 0.0f || distanceToggle != 0.0f) && /*areaPortal*/ 0) { //check to turn the areaportal on and off based on distance. + if (!gameLocal.isClient) { + CheckPlayerDistances(); + } + else { //since this is not sync'd, and we don't need to sync it, do an extra check here (for mp) + if (portalState == PORTAL_OPENED && renderEntity.customSkin && renderEntity.customSkin == declManager->FindSkin(spawnArgs.GetString( "skin_onlyWarp" ))) { + SetSkinByName( spawnArgs.GetString( "skin" ) ); + } + } + + if (distanceToggle != 0.0f) { //for pop-open portals, check fx state + if ((portalState == PORTAL_CLOSED || portalState == PORTAL_CLOSING) && bGlowPortal) { + //rww - broadcast fx while closed + if (!m_portalIdleFx.IsValid()) { + const char *portalIdleFx = spawnArgs.GetString("fx_idleclosed", "fx/portal_closed_idle"); + if (portalIdleFx[0]) { + hhFxInfo fxInfo; + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone(false); + + m_portalIdleFx = SpawnFxLocal(portalIdleFx, GetOrigin(), GetAxis(), &fxInfo, true); + if (!m_portalIdleFx.IsValid()) { //spawn failure? + gameLocal.Warning("hhPortal::Think: portal could not spawn fx for fx_idleclosed (%s).", portalIdleFx); + } + } + } + + if (m_portalIdleFx.IsValid() && !m_portalIdleFx->IsActive(TH_THINK)) { + m_portalIdleFx->Nozzle(true); + } + } + else if (m_portalIdleFx.IsValid() && m_portalIdleFx->IsActive(TH_THINK)) { //if it's not closed/closing and has fx, then stop them + m_portalIdleFx->Nozzle(false); + } + } + } + + hhAnimatedEntity::Think(); + + if (gameLocal.isMultiplayer) { //rww - if portals grab their renderEntity bounds from the animation, they can cause client-server pvs discrepancies + renderEntity.bounds[0] = idVec3(-MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS); + renderEntity.bounds[1] = idVec3(MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS); + } + + if( portalState == PORTAL_CLOSED || bNoTeleport ) { + return; + } + + // Force visuals to update until a remote renderview has been created for this portal + if ( !renderEntity.remoteRenderView ) { + BecomeActive( TH_UPDATEVISUALS ); + } + + // Bit of a hack for noclipping players: If they are close to the portal, then add them to the proximity list automatically + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->noclip ) { //rww - note that the local player is NULL for dedicated servers. + if ( (player->GetOrigin() - GetOrigin()).LengthFast() < 256.0f ) { + AddProximityEntity( player ); + } + } + + // Build a plane for the portal surface + plane.SetNormal(GetPhysics()->GetAxis()[0]); + plane.FitThroughPoint( GetPhysics()->GetOrigin() + plane.Normal() * NEAR_CLIP ); + + idVec3 origin = GetOrigin(); + idMat3 axis = GetAxis(); + + for ( i = 0; i < proximityEntities.Num(); i++ ) { + if ( !proximityEntities[i].entity.IsValid() ) { + // Remove this entity from the list + proximityEntities.RemoveIndex( i ); + continue; + } + + hit = proximityEntities[i].entity.GetEntity(); + idVec3 location = proximityEntities[i].lastPortalPoint; + idVec3 nextLocation = hit->GetPortalPoint(); + proximityEntities[i].lastPortalPoint = nextLocation; + if ( !AttemptPortal( plane, hit, location, nextLocation ) ) { + proximityEntities.RemoveIndex( i ); + + // If the entity is a player, then inform the player that they are no longer close to a portal + if ( hit->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(hit); + player->SetPortalColliding( false ); + } + } + } +} + +bool hhPortal::AttemptPortal( idPlane &plane, idEntity *hit, idVec3 location, idVec3 nextLocation ) { + + // Don't try to portal self + if( hit == this ) { + return false; + } + + if ( hit->IsBound() ) { // Do not portal bound objects -- let the master portalling handle bound entities + return false; + } + + // Don't allow idMovers to portal. TODO: Restrict other entities? + if ( hit->IsType( idMover::Type ) || hit->IsType(hhVehicle::Type) ) { //rww - do not allow shuttles either + return false; + } + + if (hit->IsType(hhPlayer::Type)) { //rww - don't portal dead players + hhPlayer *pl = static_cast(hit); + if (pl->health <= 0) { + return false; + } + + // Check if the player is intersecting this portal. If not, then don't try to portal it + if ( !GetPhysics()->GetAbsBounds().IntersectsBounds( pl->GetPhysics()->GetAbsBounds() ) ) { + return false; + } + } + + int side = plane.Side( location ); + if ( side == PLANESIDE_ON || side == PLANESIDE_CROSS ) { + side = PLANESIDE_BACK; + } + + int nextSide = plane.Side( nextLocation ); + if ( nextSide == PLANESIDE_ON || nextSide == PLANESIDE_CROSS ) { + nextSide = PLANESIDE_BACK; + } + + if( side == PLANESIDE_BACK ) { // On the backside, remove this entity from the list + return false; + } else if ( side == nextSide ) { // Entirely on one side + // Check if the entity is too far from the plane and remove it from the list + if ( !GetPhysics()->GetAbsBounds().IntersectsBounds( hit->GetPhysics()->GetAbsBounds() ) ) { + return false; + } + + return true; + } + + // Compute the location on the plane where the entity would hit + float scale; + idVec3 dir = nextLocation - location; + plane.RayIntersection( location, dir, scale ); + + // Portal the entity + PortalEntity( hit, location + dir * scale * 1.01f ); + + // Add this entity to the destination portal's proximityEntity list + if (cameraTarget && cameraTarget->IsType(hhPortal::Type)) { + hhPortal *targetPortal = static_cast(cameraTarget); + targetPortal->CollideWithPortal( hit ); // CJR PCF 04/26/06: Previously CheckPortal + } + + return false; +} + +void hhPortal::PortalProjectile( hhProjectile *projectile, idVec3 collideLocation, idVec3 nextLocation ) { + idPlane plane; + plane.SetNormal(GetPhysics()->GetAxis()[0]); + plane.FitThroughPoint( GetPhysics()->GetOrigin() ); + + AttemptPortal( plane, projectile, collideLocation, nextLocation ); +} + +//========================================================================== +//========================================================================== +void hhPortal::CheckForBuddy() { + const char * buddyName; + idEntity * foundEntity; + hhPortal* buddy; + + // Check for a buddy portal name + buddyName = spawnArgs.GetString( "partner", NULL ); + if ( buddyName ) { + gameLocal.Warning( "Portal %s has 'partner' key. Please change this to 'buddy'", GetName() ); + } + else { + buddyName = spawnArgs.GetString( "buddy" ); + } + if ( !buddyName || !buddyName[ 0 ] ) { + return; + } + + // Get the actual entity + foundEntity = gameLocal.FindEntity( buddyName ); + if ( !foundEntity ) { + return; + } + + if ( foundEntity->IsType( hhPortal::Type ) ) { + + buddy = (hhPortal *) foundEntity; + + if (buddy->spawnArgs.FindKey("buddy")) { + gameLocal.Error( "Portals %s and %s both have 'buddy' key set. Use only on master", GetName(), buddy->GetName() ); + return; + } + + // Don't link up to buddy if a monster portal, just points to portal room + if ( !monsterportal || closeDelay == 0.0f ) { + //? OK, let's be lazy, assume only 2 portals are buddied. Make us the master + buddy->SetMasterPortal( this ); + //! Set the cameraTargets of each, then update each + buddy->spawnArgs.Set( "cameraTarget", name.c_str() ); + buddy->ProcessEvent( &EV_UpdateCameraTarget ); + } + + AddSlavePortal( buddy ); + } + + spawnArgs.Set( "cameraTarget", buddyName ); + ProcessEvent( &EV_UpdateCameraTarget ); +} + + +//============================= +// hhPortal::TriggerTargets +//============================= +void hhPortal::TriggerTargets() { + const char * key = ""; + idEntity * entity = NULL; + idList< idStr > entityNames; + + + // Find the right key based on our state + if ( portalState == PORTAL_OPENING ) { + key = "targetOpening"; + } + else if ( portalState == PORTAL_OPENED ) { + key = "targetOpened"; + } + else if ( portalState == PORTAL_CLOSING ) { + key = "targetClosing"; + } + else if ( portalState == PORTAL_CLOSED ) { + key = "targetClosed"; + } + + //gameLocal.Printf( "Keying %s\n", key ); + + // Loop through the entities, spawning each one + hhUtils::GetValues( spawnArgs, key, entityNames, true ); + + for ( int i = 0; i < entityNames.Num(); ++i ) { + //gameLocal.Printf( "Gonna trigger %s\n", entityNames[ i ].c_str() ); + entity = gameLocal.FindEntity( entityNames[ i ] ); + if ( entity ) { + entity->PostEventMS( &EV_Activate, 0, this ); + } + } +} + + +//========================================================================== +// +// hhPortal::CheckPortal +// +// If this entity can be portaled, typically called from the low-level physics clip functions +// +// CJR PCF 04/26/06: Changed this function to separate CheckPortal and CollideWithPortal +//========================================================================== + +bool hhPortal::CheckPortal( const idEntity *other, int contentMask ) { + if ( contentMask & CONTENTS_GAME_PORTAL ) { // Check if the other entity should clip against the portal + return false; + } + + if ( !other ) { + return true; + } + + if ( other->fl.noPortal ) { + return false; // Do not allow this entity to portal, make it collide with the portal instead + } + + return true; +} + +bool hhPortal::CheckPortal( const idClipModel *mdl, int contentMask ) { + if ( contentMask & CONTENTS_GAME_PORTAL ) { // Check if the other entity should clip against the portal + return false; + } + + if ( !mdl ) { + return true; + } + + return CheckPortal( mdl->GetEntity(), contentMask ); +} + +//========================================================================== +// +// hhPortal::CollideWithPortal +// +// Called from low-level clip functions, actually collide the entity with the portal +// +// CJR PCF 04/26/06: Formerly part of CheckPortal +//========================================================================== + +void hhPortal::CollideWithPortal( const idEntity *other ) { + if ( !other ) { + return; + } + + if ( other->IsType( hhProjectile::Type ) ) { // Projectiles move so fast that they should get portaled next time they think + // Only add this projectile to the collided list if it hits the front side of the portal + idPlane plane; + plane.SetNormal(GetPhysics()->GetAxis()[0]); + plane.FitThroughPoint( GetPhysics()->GetOrigin() ); + + int side = plane.Side( other->GetOrigin() ); + if ( side == PLANESIDE_FRONT ) { // It's on the front, so add this portal to the list + hhProjectile *projectile = (hhProjectile *)(other); + + // Check if the projectile will actually impact the portal + idVec3 end = projectile->GetOrigin() + projectile->GetPhysics()->GetLinearVelocity(); + + if ( plane.LineIntersection( projectile->GetOrigin(), end ) ) { // Projectile collides with the portal + projectile->SetCollidedPortal( this, projectile->GetPortalPoint(), projectile->GetPhysics()->GetLinearVelocity() ); + } + } + } else { + AddProximityEntity( other ); + } +} + +void hhPortal::CollideWithPortal( const idClipModel *mdl ) { + if ( !mdl ) { + return; + } + + CollideWithPortal( mdl->GetEntity() ); +} + +//========================================================================== +// +// hhPortal::AddProximityEntity +// +// Saves this entity on a list, and will check if it can be portalled +// the next time the portal thinks +//========================================================================== + +void hhPortal::AddProximityEntity( const idEntity *other) { + // Go through the list and guarantee that this entity isn't in multiple times + // note: cannot use IdList::AddUnique, because the lastPortalPoint might be different during this add + for( int i = 0; i < proximityEntities.Num(); i++ ) { + if ( proximityEntities[i].entity.GetEntity() == other ) { + return; + } + } + + // Add this entity to the potential portal list + proximityEntity_t prox; + prox.entity = other; + prox.lastPortalPoint = ((idEntity *)(other))->GetPortalPoint(); + + proximityEntities.Append( prox ); + + // If the entity is a player, then inform the player that they are close to this portal + // needed for weapon projectile firing + if ( other->IsType( hhPlayer::Type ) ) { + hhPlayer *player = (hhPlayer *)(other); + + player->SetPortalColliding( true ); + } +} + +//========================================================================== +// +// hhPortal::PortalEntity +// +// To rotate a vector from one portal space into another: +// transform vector into Source Portal Space (mul by axis transpose) +// flip X & Y (only flip X for angles, not for position) +// transform vector into Destination Portal Space +//========================================================================== + +void PortalRotate( idVec3 &vec, const idMat3 &sourceTranspose, const idMat3 &dest, const bool flipX ) { + vec *= sourceTranspose; + vec.y *= -1; + if ( flipX ) { + vec.x *= -1; + } + vec *= dest; +} + +bool hhPortal::PortalEntity( idEntity *ent, const idVec3 &point ) { + idMat3 sourceAxis; + idMat3 destAxis; + idMat3 newEntAxis; + + if ( !ent ) { + return(false); + } + + if ( cameraTarget ) { + sourceAxis = GetAxis().Transpose(); + destAxis = cameraTarget->GetAxis(); + + // Compute new location + idVec3 newLocation = point - GetOrigin(); + PortalRotate( newLocation, sourceAxis, destAxis, false ); + newLocation += cameraTarget->GetOrigin(); + + // Compute new axis + newEntAxis = ent->GetAxis(); + + // Rotate the vector into new portal space + PortalRotate( newEntAxis[0], sourceAxis, destAxis, true ); + PortalRotate( newEntAxis[1], sourceAxis, destAxis, true ); + PortalRotate( newEntAxis[2], sourceAxis, destAxis, true ); + + // Actually attempt to portal the entity + if ( PortalTeleport( ent, newLocation, newEntAxis, sourceAxis, destAxis ) ) { + if ( alertMonsters && !gameLocal.isMultiplayer && ent->IsType( idPlayer::Type ) ) { + gameLocal.SendMessageAI( this, GetOrigin(), 2000, MA_EnemyPortal ); + } + ent->Portalled( this ); // Inform the actor that it was just portalled + return(true); // Portal succeeded + } + } + + return(false); // Portal failed +} + +//========================================================================== +// +// hhPortal::PortalTeleport +// +//========================================================================== + +bool hhPortal::PortalTeleport( idEntity *ent, const idVec3 &origin, const idMat3 &axis, const idMat3 &sourceAxis, const idMat3 &destAxis ) { + idClipModel *clip = ent->GetPhysics()->GetClipModel(); + + if ( !clip ) { + return false; + } + + // Properly set velocity relative to the new portal + idVec3 vel = ent->GetPhysics()->GetLinearVelocity(); + PortalRotate( vel, sourceAxis, destAxis, true ); + ent->GetPhysics()->SetLinearVelocity( vel ); + + //rww - check if this new orientation is going to be in solid or not. if it is, try displacing the new origin based on the portal + idVec3 useOrigin = origin; + trace_t transCheck; + if (gameLocal.clip.TranslationWithExceptions(transCheck, useOrigin, useOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + if (cameraTarget) { + bool safeSpot = true; + const float distExtrusion = 2.0f; + const float heightAdjust = (ent->GetPhysics()->GetBounds()[1].z-fabsf(ent->GetPhysics()->GetBounds()[0].z))/2.0f; + idVec3 testOrigin = cameraTarget->GetOrigin(); + testOrigin += cameraTarget->GetAxis()[0]*distExtrusion; + testOrigin -= cameraTarget->GetAxis()[2]*heightAdjust; + if (gameLocal.clip.TranslationWithExceptions(transCheck, testOrigin, testOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + testOrigin += cameraTarget->GetAxis()[2]*(heightAdjust*2.0f); //then try going up further (could be upside-down or something) + + if (gameLocal.clip.TranslationWithExceptions(transCheck, testOrigin, testOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + //damn, this portal is really busted. + safeSpot = false; + } + } + if (safeSpot) { + useOrigin = testOrigin; //got a safe spot + if (ent->IsType(hhPlayer::Type)) { + hhPlayer *plEnt = static_cast(ent); + if (plEnt->IsWallWalking()) { //if it's a wallwalking player, try to trace down on the other side for wallwalk + idVec3 downPoint = useOrigin - (axis[2]*64.0f); + if (gameLocal.clip.Translation(transCheck, useOrigin, downPoint, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) { + if (gameLocal.GetMatterType(transCheck, NULL) == SURFTYPE_WALLWALK) { //if we hit wallwalk, use the trace endpoint + useOrigin = transCheck.endpos; + } + } + } + } + } + else { + testOrigin = cameraTarget->GetOrigin(); + testOrigin += cameraTarget->GetAxis()[0]*distExtrusion; + testOrigin -= cameraTarget->GetAxis()[2]*heightAdjust; + + useOrigin = testOrigin; + +#if !GOLD + if (developer.GetBool()) { + const char *entName = ent->GetName(); + if (!entName || !entName[0]) { + entName = ""; + } + gameLocal.Warning("Ent '%s' could not get a clean trace on the other side of gameportal at (%f %f %f).", entName, useOrigin.x, useOrigin.y, useOrigin.z); + hhUtils::DebugAxis( testOrigin, cameraTarget->GetAxis(), 32.0f, 5000 ); + } +#endif + } + } + } + + // If the actor is a player, then disable camera interpolation for this move. This must be done before linking + if( ent->IsType(hhPlayer::Type) ) { + hhPlayer* player = static_cast( ent ); + + + idVec3 viewDir = player->GetAxis()[0]; + PortalRotate( viewDir, sourceAxis, destAxis, true ); + player->TeleportNoKillBox( useOrigin, axis, viewDir, player->GetUntransformedViewAngles() ); + } else { + // Valid move. This code is done in SetOrientation for the player. + ent->SetOrigin( useOrigin ); + ent->SetAxis( axis ); + } + + // Re-link the actor into the clip tree + clip->Link( gameLocal.clip ); + + if ( ent->IsType( idActor::Type ) ) { + static_cast(ent)->LinkCombat(); + } + + // players telefrag anything at the new position + if ( ent->IsType( hhPlayer::Type ) ) { + gameLocal.KillBox( ent ); + } + + // Set the gravity correctly on the entity that was portaled, based upon the destination portal's gravity + ent->CancelEvents( &EV_ResetGravity ); + + if (!ent->IsType(hhVehicle::Type)) { + ent->SetGravity( cameraTarget->GetGravity() ); + } + + // If the actor is a player, then check for wallwalk. This should be done after linking and setting gravity + if( ent->IsType(hhPlayer::Type) ) { + hhPlayer* player = static_cast(ent); + if (player->GetPhysics() && player->GetPhysics()->IsType(hhPhysics_Player::Type)) { + static_cast(player->GetPhysics())->CheckWallWalk( true ); + } + } + + // Sound issues + if ( bGlowPortal ) { + StartSound( "snd_portal_entity", SND_CHANNEL_ANY ); // Play the portal sound at the origin portal + if(this->cameraTarget) { // Play the portal sound at the destination portal as well + const idSoundShader *def = declManager->FindSound(spawnArgs.GetString("snd_portal_entity"));//gameSoundWorld->FinishShader(spawnArgs.GetString("snd_portal_entity")); + this->cameraTarget->StartSoundShader( def, SND_CHANNEL_ANY ); + } + } + + return(true); +} + +//========================================================================== +// +// hhPortal::Event_Trigger +// +// Toggle portal state. +//========================================================================== + +void hhPortal::Event_Trigger(idEntity *activator) { + + // If we have a master portal, have him trigger everyone + if ( masterPortal.IsValid() ) { + masterPortal->Event_Trigger( activator ); + return; + } + + // If we have slave portals, trigger them first + if ( slavePortals.Num() > 0 ) { + for ( int i = 0; i < slavePortals.Num(); ++i ) { + slavePortals[ i ]->Trigger( activator ); + } + } + + Trigger( activator ); +} + + +void hhPortal::Trigger( idEntity *activator ) { + + hhFxInfo fxInfo; + + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + + if(portalState == PORTAL_CLOSED) { + portalState = PORTAL_OPENING; + if (!bNoTeleport) { //rww - if teleporting, ensure things collide with me while i am opening + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +#if GAMEPORTAL_PVS + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } +#endif + TriggerTargets(); // nla + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = MS2SEC( gameLocal.time ); + + if ( bGlowPortal ) { + if (!gameLocal.isMultiplayer) { //rww - superfast portals don't use fx + BroadcastFxInfo( spawnArgs.GetString("fx_open"), GetOrigin(), GetAxis(), &fxInfo ); + } + + StartSound( "snd_open", SND_CHANNEL_ANY ); + + if ( spawnArgs.GetBool( "fast_open", "0" ) ) { + SetSkinByName( spawnArgs.GetString( "skin" ) ); + } else { + SetSkinByName( spawnArgs.GetString( "skin_onlyWarp" ) ); // Only show the warp when first opening + } + + int anim = GetAnimator()->GetAnim(spawnArgs.GetString("open_anim", "open")); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + PostEventMS( &EV_Opened, GetAnimator()->AnimLength( anim ) ); + SetShaderParm( SHADERPARM_MODE, 0 ); // ensure that sparking is off + + fl.neverDormant = true; // Don't allow the portal to go dormant while opening/closing + + } else { + PostEventMS( &EV_Opened, 500 ); + } + + PostEventMS( &EV_Show, 10 ); // Delay showing for a frame so the skin and registers are properly set beforehand + } else if(portalState == PORTAL_OPENED || portalState == PORTAL_OPENING) { + portalState = PORTAL_CLOSING; + TriggerTargets(); // nla + + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + + if ( bGlowPortal ) { + if (!gameLocal.isMultiplayer) { //rww - superfast portals don't use fx + BroadcastFxInfo( spawnArgs.GetString("fx_close"), GetOrigin(), GetAxis(), &fxInfo ); + } + + StopSound( SND_CHANNEL_ANY ); + StartSound( "snd_close", SND_CHANNEL_ANY ); + + int anim = GetAnimator()->GetAnim("close"); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0.5 ); + PostEventMS( &EV_Closed, GetAnimator()->AnimLength( anim ) ); + + fl.neverDormant = true; // Don't allow the portal to go dormant while opening/closing + + } else { + PostEventMS( &EV_Closed, 400 ); + } + } +} + +//========================================================================== +// +// hhPortal::Event_Opened +// +//========================================================================== + +void hhPortal::Event_Opened( void ) { + portalState = PORTAL_OPENED; + if (!bNoTeleport) { //rww - if teleporting, ensure things collide with me while i am open + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + TriggerTargets(); // nla + + if ( bGlowPortal ) { + StartSound( "snd_loop", SND_CHANNEL_ANY ); + int anim = GetAnimator()->GetAnim("opened"); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); + + fl.neverDormant = false; // The portal can go dormant once opened/closed + + PostEventSec( &EV_PortalSpark, gameLocal.random.RandomFloat() ); + } + + UpdateVisuals(); + + // If we are open, and should close automatically, post an event to trigger us closed - nla + if ( closeDelay > 0.0f ) { + PostEventSec( &EV_Activate, closeDelay, NULL ); + + // Remove the portal now that it has done it's job + if (monsterportal) { +#ifdef _DEBUG + // If another portal is camera targetting me it will cause a crash so check for it + for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if (ent->cameraTarget && ent->cameraTarget == this) { + assert(0); + } + } + +#endif + PostEventSec( &EV_Remove, closeDelay+5.0f ); + } + } +} + +//========================================================================== +// +// hhPortal::Event_Closed +// +//========================================================================== + +void hhPortal::Event_Closed( void ) { + renderEntity.shaderParms[SHADERPARM_MODE] = 1.0f; + portalState = PORTAL_CLOSED; + GetPhysics()->SetContents( 0 ); //rww - do not collide with things while closed +#if GAMEPORTAL_PVS + if (areaPortal && !gameLocal.isClient) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + } +#endif + TriggerTargets(); // nla + + fl.neverDormant = false; // The portal can go dormant once opened/closed + + Hide(); + UpdateVisuals(); + + //? Have an option to remove the portals? - nla + if ( spawnArgs.GetBool( "remove_on_close", "0" ) ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +//========================================================================== +// +// hhPortal::Event_PortalSpark +// +//========================================================================== + +void hhPortal::Event_PortalSpark( void ) { + int spark; + float nextTime; + + if ( this->IsHidden() ) { // No sparking if hidden + SetShaderParm( SHADERPARM_MODE, 0 ); // set the material parm (one-based) + return; + } + + spark = gameLocal.random.RandomInt( spawnArgs.GetInt( "sparkCount" ) ); + + // Set the shader parm as needed + SetShaderParm( SHADERPARM_MODE, spark + 1 ); // set the material parm (one-based) + + if ( gameLocal.random.RandomFloat() < 0.1f ) { // 1/10th of a chance that another spark will happen quickly after this one + nextTime = 0.2f; + } else { // Normal random time between sparks. + nextTime = spawnArgs.GetFloat( "sparkTimeMin" ) + gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "sparkTimeRnd" ); + } + + StartSound( "snd_portal_spark", SND_CHANNEL_ANY ); + + PostEventSec( &EV_PortalSpark, nextTime ); + PostEventSec( &EV_PortalSparkEnd, 0.1f ); // spark only lasts for 0.1 sec + +} + +//========================================================================== +// +// hhPortal::Event_PortalSparkEnd +// +//========================================================================== + +void hhPortal::Event_PortalSparkEnd( void ) { + SetShaderParm( SHADERPARM_MODE, 0 ); // Disable the spark parm +} + +//========================================================================== +// +// hhPortal::Event_ShowGlowPortal +// +// Simply sets the skin on the portal back to the default or to the +// specified "skin" +//========================================================================== + +void hhPortal::Event_ShowGlowPortal( void ) { + SetSkinByName( spawnArgs.GetString( "skin" ) ); +} + +//========================================================================== +// +// hhPortal::Event_HideGlowPortal +// +// Sets the skin on the portal to a specific skin that hides the glowy parts +//========================================================================== + +void hhPortal::Event_HideGlowPortal( void ) { + SetSkinByName( spawnArgs.GetString( "skin_onlyWarp" ) ); +} diff --git a/neo/prey/Prey/game_portal.h b/neo/prey/Prey/game_portal.h new file mode 100644 index 00000000..cab6fad5 --- /dev/null +++ b/neo/prey/Prey/game_portal.h @@ -0,0 +1,125 @@ + +#ifndef __GAME_PORTAL_H__ +#define __GAME_PORTAL_H__ + +#if GAMEPORTAL_PVS + +// Allows us to create a PVS link between two areas +class hhArtificialPortal : public idEntity { + CLASS_PROTOTYPE( hhArtificialPortal ); +public: + void Spawn(); + void SetPortalState(bool openPVS, bool openSound); + + void Save(idSaveGame *savefile) const; + void Restore(idRestoreGame *savefile); + +protected: + void Event_SetPortalState(bool openPVS, bool openSound); + + qhandle_t areaPortal; // 0 = no portal +}; + +#endif + +typedef struct proximityEntity_s { + idEntityPtr entity; + idVec3 lastPortalPoint; +} proximityEntity_t; + +// jsh - Changed from idEntity to hhAnimatedEntity +class hhPortal : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhPortal ); + + typedef enum { + PORTAL_OPENING, + PORTAL_OPENED, + PORTAL_CLOSING, + PORTAL_CLOSED, + } portalStates_t; + + + hhPortal(void); + ~hhPortal(); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void CheckPlayerDistances(void); + void Think( void ); + bool PortalEntity( idEntity *ent, const idVec3 &point ); + bool PortalTeleport( idEntity *ent, const idVec3 &origin, const idMat3 &axis, const idMat3 &sourceAxis, const idMat3 &destAxis ); + bool IsActive(void) { return(portalState < PORTAL_CLOSING); } + virtual void SetGravity( const idVec3& newGravity ) { portalGravity = newGravity; } + const idVec3& GetGravity( void ) const { return portalGravity; } + void PostSpawn( void ); + + bool AttemptPortal( idPlane &plane, idEntity *hit, idVec3 location, idVec3 nextLocation ); + + void PortalProjectile( hhProjectile *projectile, idVec3 collideLocation, idVec3 nextLocation ); + + virtual bool CheckPortal( const idEntity *other, int contentMask ); + virtual bool CheckPortal( const idClipModel *mdl, int contentMask ); + virtual void CollideWithPortal( const idEntity *other ); // CJR PCF 04/26/06 + virtual void CollideWithPortal( const idClipModel *mdl ); // CJR PCF 04/26/06 + + void AddProximityEntity( const idEntity *other); + +protected: + void Event_Opened( void ); + void Event_Closed( void ); + void Event_Trigger(idEntity *activator); + void Event_ResetGravity() { portalGravity = hhUtils::GetLocalGravity(GetOrigin(), GetPhysics()->GetBounds(), gameLocal.GetGravity() ); } + + void Event_PortalSpark( void ); + void Event_PortalSparkEnd( void ); + + void Event_ShowGlowPortal( void ); + void Event_HideGlowPortal( void ); + + // Actual logic for the trigger functionality. (Needed to break out for partering :) + void Trigger( idEntity *activator ); + + // Methods used to sync up portals + hhPortal * GetMasterPortal() { return( masterPortal.GetEntity() ); } + void SetMasterPortal( hhPortal *master ) { masterPortal = master; } + void AddSlavePortal( hhPortal *slave ) { slavePortals.Append( slave ); } + void CheckForBuddy(); + + void TriggerTargets(); + +private: +#if GAMEPORTAL_PVS + qhandle_t areaPortal; // 0 = no portal +#endif + + portalStates_t portalState; + + bool bNoTeleport; // Is purely a visual portal. Will not try to teleport anything near it + bool bGlowPortal; // This portal has visual/audio effects + + idVec3 portalGravity; // Local gravity to this portal. Will be changed by any zones the portal is within + + float closeDelay; // Secs to wait before automatically closing when opened - nla + float distanceToggle; //rww - toggles the associated game portal on and off based on nearest player distance, 0.0 means not enabled, otherwise value is the distance to turn on at. + float distanceCull; //rww - distance to shut off the vis gameportal but not the render portal + bool areaPortalCulling; //rww - if the portal is currently "culled" (off) due to distanceCull checking + bool monsterportal; + bool alertMonsters; + + idList > slavePortals; + idEntityPtr masterPortal; + + idList proximityEntities; + + idEntityPtr m_portalIdleFx; //rww - primarily for mp, the effect closed pop-open portals play +}; + +#endif /* __GAME_PORTAL_H__ */ diff --git a/neo/prey/Prey/game_portalframe.cpp b/neo/prey/Prey/game_portalframe.cpp new file mode 100644 index 00000000..f7025101 --- /dev/null +++ b/neo/prey/Prey/game_portalframe.cpp @@ -0,0 +1,20 @@ +// hhPortalFrame +// +// Frame for our portal which accepts commands from GUIs which change shader parm5 +// based on the command, as well as trigger some given entities each level, and finally +// trigger all it's own targets upon the victory command + + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION(idEntity, hhPortalFrame) +END_CLASS + + +void hhPortalFrame::Spawn() { + GetPhysics()->SetContents(CONTENTS_SOLID); +} + diff --git a/neo/prey/Prey/game_portalframe.h b/neo/prey/Prey/game_portalframe.h new file mode 100644 index 00000000..d23e7c84 --- /dev/null +++ b/neo/prey/Prey/game_portalframe.h @@ -0,0 +1,14 @@ + +#ifndef __GAME_PORTALFRAME_H__ +#define __GAME_PORTALFRAME_H__ + + +class hhPortalFrame : public idEntity { +public: + CLASS_PROTOTYPE( hhPortalFrame ); + + void Spawn( void ); +}; + + +#endif /* __GAME_PORTALFRAME_H__ */ diff --git a/neo/prey/Prey/game_proxdoor.cpp b/neo/prey/Prey/game_proxdoor.cpp new file mode 100644 index 00000000..336dae93 --- /dev/null +++ b/neo/prey/Prey/game_proxdoor.cpp @@ -0,0 +1,1163 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +const idEventDef EV_PollForExit("", NULL); +const float proxDoorRefreshMS = 50.f; //refresh rate of door while open (controls how frequently to check if someone is still close enough) + + +ABSTRACT_DECLARATION( idEntity, hhProxDoorSection ) +END_CLASS + +void hhProxDoorSection::Spawn( void ) { + fl.networkSync = true; + proximity = 0.0f; + hasNetData = false; + proxyParent = NULL; +} + +void hhProxDoorSection::ClientPredictionThink( void ) { + Show(); + idEntity::ClientPredictionThink(); +} + +void hhProxDoorSection::WriteToSnapshot( idBitMsgDelta &msg ) const { + WriteBindToSnapshot(msg); + GetPhysics()->WriteToSnapshot(msg); +} + +void hhProxDoorSection::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ReadBindFromSnapshot(msg); + GetPhysics()->ReadFromSnapshot(msg); + + if (msg.HasChanged()) { + Show(); + UpdateVisuals(); + Present(); + GetPhysics()->LinkClip(); + } +} + + + +//------------------------------------------------------------------------------------------------- +// hhProxDoor. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( idEntity, hhProxDoor ) + EVENT( EV_Touch, hhProxDoor::Event_Touch ) + EVENT( EV_PollForExit, hhProxDoor::Event_PollForExit ) + EVENT( EV_PostSpawn, hhProxDoor::Event_PostSpawn ) + EVENT( EV_Activate, hhProxDoor::Event_Activate ) +END_CLASS + +//-------------------------------- +// hhProxDoor::hhProxDoor +//-------------------------------- +hhProxDoor::hhProxDoor() { + areaPortal = 0; + proxState = PROXSTATE_Inactive; + doorTrigger = NULL; + aas_area_closed = false; + hasNetData = false; + sndTrigger = NULL; + nextSndTriggerTime = 0; + //HUMANHEAD PCF rww 06/06/06 - fix for snappy prox doors + lastAmount = 0.0f; + //HUMANHEAD END +} + +//-------------------------------- +// hhProxDoor::hhProxDoor +//-------------------------------- +hhProxDoor::~hhProxDoor() { + if (doorTrigger) { + doorTrigger->Unlink(); + delete doorTrigger; + doorTrigger = NULL; + } + if ( sndTrigger ) { + sndTrigger->Unlink(); + delete sndTrigger; + sndTrigger = NULL; + } +} + +//-------------------------------- +// hhProxDoor::Spawn +//-------------------------------- +void hhProxDoor::Spawn() { + proxState = PROXSTATE_Inactive; + doorSndState = PDOORSND_Closed; + + spawnArgs.GetBool( "startlocked", "0", doorLocked ); + SetShaderParm( SHADERPARM_MODE, GetDoorShaderParm( doorLocked, true ) ); // 2=locked, 1=unlocked, 0=never locked + + if( !spawnArgs.GetFloat("trigger_distance", "0.0", maxDistance) ) { + common->Warning( "No 'trigger_distance' specified for entityDef '%s'", this->GetEntityDefName() ); + } + if( !spawnArgs.GetFloat("movement_distance", "0.0", movementDistance) ) { + common->Warning( "No 'movement_distance' specified for entityDef '%s'", this->GetEntityDefName() ); + } + lastDistance = movementDistance + 1.0f; // Safe default -mdl + + if( !spawnArgs.GetFloat("stop_distance", "0.0", stopDistance) ) { + common->Warning( "No 'stop_distance' specificed for entityDef '%s'", this->GetEntityDefName() ); + } + + spawnArgs.GetBool( "openForMonsters", "1", openForMonsters ); + spawnArgs.GetFloat( "damage", "1", damage ); + + //Find any portal boundary we are on + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + if (gameLocal.isMultiplayer) { + fl.networkSync = true; + + if (gameLocal.isClient) { + doorTrigger = new idClipModel( idTraceModel(idBounds(vec3_origin).Expand(maxDistance)) ); + doorTrigger->Link( gameLocal.clip, this, 255, GetOrigin(), GetAxis() ); + doorTrigger->SetContents( CONTENTS_TRIGGER ); + + SetAASAreaState( doorLocked ); //close the area state if we are locked... + SetDoorState( PROXSTATE_Inactive ); + } + else { + PostEventMS( &EV_PostSpawn, 50 ); //rww - this must be delayed or it will happen on the first server frame and we DON'T want that. + } + } + else { //just call it now. some sp maps might target a door piece in the spawn function of an entity or something crazy like that. + Event_PostSpawn(); + } +} + +//-------------------------------- +// hhProxDoor::Event_PostSpawn +//-------------------------------- +void hhProxDoor::Event_PostSpawn( void ) { + int numSubObjs; + int i; + const char* objDef; + +//Parse and spawn our door sections + numSubObjs = spawnArgs.GetInt( "num_doorobjs", "0" ); + for( i = 0; i < numSubObjs; i++ ) { + if( !spawnArgs.GetString( va("doorobject%i", i+1), "", &objDef ) ) { + common->Warning( "failed to find doorobject%i", i+1 ); + } + else { + //Set our default rotation/origin. + idDict args; + args.SetVector( "origin", this->GetOrigin() ); + args.SetMatrix( "rotation", this->GetAxis() ); + hhProxDoorSection* ent = static_cast ( gameLocal.SpawnObject(objDef, &args) ); + if( !ent ) { + common->Warning( "failed to spawn doorobject%i for entityDef '%s'", i+1, GetEntityDefName() ); + } + else { + ent->proxyParent = this; + doorPieces.Append( ent ); + } + } + } +//Spawn our trigger and link it up +// if( doorLocked && !spawnArgs.GetBool("locktrigger") ) { + //we would never be able to open the door, so don't spawn a trigger +// } +// else { + doorTrigger = new idClipModel( idTraceModel(idBounds(vec3_origin).Expand(maxDistance)) ); + doorTrigger->Link( gameLocal.clip, this, 255, GetOrigin(), GetAxis() ); + doorTrigger->SetContents( CONTENTS_TRIGGER ); +// } + + SpawnSoundTrigger(); + + SetAASAreaState( doorLocked ); //close the area state if we are locked... + SetDoorState( PROXSTATE_Inactive ); +} + +void hhProxDoor::SpawnSoundTrigger() { + idBounds bounds,localbounds; + int i; + int best; + + // Since models bounds are overestimated, we need to use the bounds from the + // clipmodel, which was set before the over-estimation + localbounds = GetPhysics()->GetBounds(); + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( localbounds[1][ i ] - localbounds[0][ i ] < localbounds[1][ best ] - localbounds[0][ best ] ) { + best = i; + } + } + localbounds[1][ best ] += 50; + localbounds[0][ best ] -= 50; + + // Now transform into absolute coordintates + if ( GetPhysics()->GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( localbounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + } + else { + bounds[0] = localbounds[0] + GetPhysics()->GetOrigin(); + bounds[1] = localbounds[1] + GetPhysics()->GetOrigin(); + } + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); + + // create a trigger clip model + sndTrigger = new idClipModel( idTraceModel( bounds ) ); + sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity ); + sndTrigger->SetContents( CONTENTS_TRIGGER ); +} + +void hhProxDoor::Save(idSaveGame *savefile) const { + int i; + + savefile->WriteInt( proxState ); + savefile->WriteInt( doorSndState ); + + savefile->WriteInt( doorPieces.Num() ); // idList + for (i=0; iWriteObject( doorPieces[i] ); + doorPieces[i].Save(savefile); + } + + savefile->WriteClipModel( doorTrigger ); + savefile->WriteFloat( entDistanceSq ); + savefile->WriteFloat( maxDistance ); + savefile->WriteFloat( movementDistance ); + savefile->WriteFloat( stopDistance ); + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteBool( doorLocked ); + savefile->WriteFloat( lastAmount ); + savefile->WriteBool( openForMonsters ); + savefile->WriteBool( aas_area_closed ); + savefile->WriteFloat( lastDistance ); + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); +} + +void hhProxDoor::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( (int &)proxState ); + savefile->ReadInt( (int &)doorSndState ); + + doorPieces.Clear(); + savefile->ReadInt( num ); // idList + doorPieces.SetNum( num ); + for (i=0; iReadObject( reinterpret_cast(doorPieces[i]) ); + doorPieces[i].Restore(savefile); + } + + savefile->ReadClipModel( doorTrigger ); + savefile->ReadFloat( entDistanceSq ); + savefile->ReadFloat( maxDistance ); + savefile->ReadFloat( movementDistance ); + savefile->ReadFloat( stopDistance ); + savefile->ReadInt( (int &)areaPortal ); + if ( areaPortal ) { + int portalState; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadBool( doorLocked ); + savefile->ReadFloat( lastAmount ); + savefile->ReadBool( openForMonsters ); + savefile->ReadBool( aas_area_closed ); + savefile->ReadFloat( lastDistance ); + + SetAASAreaState( aas_area_closed ); + + spawnArgs.GetFloat( "damage", "1", damage ); + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); +} + +void hhProxDoor::ClientPredictionThink( void ) { + Think(); +} + +void hhProxDoor::WriteToSnapshot( idBitMsgDelta &msg ) const { + WriteBindToSnapshot(msg); + GetPhysics()->WriteToSnapshot(msg); + + msg.WriteBits(doorPieces.Num(), 8); + for (int i = 0; i < doorPieces.Num(); i++) { + msg.WriteBits(doorPieces[i].GetSpawnId(), 32); + } + + msg.WriteBits(proxState, 8); + + msg.WriteFloat(lastAmount); + + msg.WriteBits(aas_area_closed, 1); + + //don't need this, predicting door movement. + //msg.WriteBits(doorSndState, 8); +} + +void hhProxDoor::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ReadBindFromSnapshot(msg); + GetPhysics()->ReadFromSnapshot(msg); + + int num = msg.ReadBits(8); + doorPieces.SetNum(num); + for (int i = 0; i < num; i++) { + int spawnId = msg.ReadBits(32); + if (!spawnId) { + doorPieces[i] = NULL; + } + else { + doorPieces[i].SetSpawnId(spawnId); + } + } + + EProxState newProxState = (EProxState)msg.ReadBits(8); + if (proxState != newProxState) { + SetDoorState(newProxState); + } + + lastAmount = msg.ReadFloat(); + + bool closed = !!msg.ReadBits(1); + if (aas_area_closed != closed) { + SetAASAreaState(closed); + } + + /* + EPDoorSound newSndState = (EPDoorSound)msg.ReadBits(8); + if (newSndState != doorSndState) { + UpdateSoundState(newSndState); + } + */ + + hasNetData = true; +} + +//-------------------------------- +// hhProxDoor::Think +//-------------------------------- +void hhProxDoor::Ticker( void ) { + int i; + float bestDistSq, bestDist = idMath::INFINITY, amount; + + //HUMANHEAD PCF mdl 04/27/06 - Locked doors are forced inactive + if ( doorLocked && proxState == PROXSTATE_Active ) { + proxState = PROXSTATE_GoingInactive; + } + + if ( proxState == PROXSTATE_GoingInactive ) { + float fullyOpen = (movementDistance - stopDistance) / movementDistance; + //HUMANHEAD PCF mdl 04/27/06 - Added 30 HZ multiplier + float step = (0.2f * ( 1.0f / ( 1000.0f / 60.0f ) ) ) * (60.0f * USERCMD_ONE_OVER_HZ); + float low = idMath::ClampFloat( 0.0f, 1.0f, lastAmount - step ); + float high = idMath::ClampFloat( 0.0f, 1.0f, lastAmount + step ); + amount = idMath::ClampFloat( low, high, 0.0f ); + //HUMANHEAD PCF mdl 04/27/06 - Removed old lock code that was here + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { //rww - added in case something decides to remove one of the pieces externally. + if (gameLocal.isClient && areaPortal && gameRenderWorld->GetPortalState(areaPortal) != PS_BLOCK_NONE) { + amount = 0.0f; //rww - hax for portal state sync'ing on client and server + } + doorPieces[ i ]->SetProximity( amount ); + } + } + + if ( amount == 0.0f ) { + SetDoorState( PROXSTATE_Inactive ); + UpdateSoundState( PDOORSND_Closed ); + } else { + UpdateSoundState( PDOORSND_Closing ); + } + + } else { + + bestDist = movementDistance; + bestDistSq = PollClosestEntity(); + if( bestDistSq >= 0.f ) { + bestDist = idMath::Sqrt( bestDistSq ); + } + + if ( bestDistSq == -1.0f ) { + SetDoorState( PROXSTATE_GoingInactive ); + return; + } + + // Set the default amount to no change + amount = lastAmount; + if( bestDist < movementDistance ) { + float fullyOpen = (movementDistance - stopDistance) / movementDistance; + amount = idMath::ClampFloat( 0.f, fullyOpen, (1.f - ( bestDist / movementDistance )) ); + //HUMANHEAD PCF mdl 04/27/06 - Added 30 HZ multiplier + float step = (0.2f * ( 1.0f / ( 1000.0f / 60.0f ) ) ) * (60.0f * USERCMD_ONE_OVER_HZ); + float low = idMath::ClampFloat( 0.0f, 1.0f, lastAmount - step ); + float high = idMath::ClampFloat( 0.0f, 1.0f, lastAmount + step ); + amount = idMath::ClampFloat( low, high, amount ); + if ( doorLocked ) { + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { + doorPieces[ i ]->SetProximity( 0.0f ); + } + } + } else { + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { //rww - added in case something decides to remove one of the pieces externally. + if (gameLocal.isClient && areaPortal && gameRenderWorld->GetPortalState(areaPortal) != PS_BLOCK_NONE) { + amount = 0.0f; //rww - hax for portal state sync'ing on client and server + } + doorPieces[ i ]->SetProximity( amount ); + } + } + } + if( lastAmount == -1 && amount >= 0.f ) { //was closed, just starting to open + UpdateSoundState( PDOORSND_Opened ); + } + else if( lastAmount > amount ) { //closing + UpdateSoundState( PDOORSND_Closing ); + } + else if( lastAmount < amount ) { //opening + UpdateSoundState( PDOORSND_Opening ); + } + else if( amount == fullyOpen ) { //fully open + UpdateSoundState( PDOORSND_FullyOpened ); + } + else { //stop sounds, since we aren't moving + UpdateSoundState( PDOORSND_Stopped ); + } + } + else { + SetDoorState( PROXSTATE_GoingInactive ); + UpdateSoundState( PDOORSND_Closing ); + } + } + + // If we're closing and 75% closed or more, crush anything unlucky enough to be inside us (don't do this on the client -rww) + if ( !gameLocal.isClient && lastAmount < 0.25f && lastAmount != -1.0f && amount != -1.0f && amount < lastAmount ) { + CrushEntities(); + } + lastAmount = amount; + lastDistance = bestDist; +} + +void hhProxDoor::UpdateSoundState( EPDoorSound newState ) { + if( doorSndState == newState ) { + return; + } + switch( newState ) { + case PDOORSND_Opening: + StopSound( SND_CHANNEL_BODY ); + StartSound( "snd_closing", SND_CHANNEL_BODY ); + break; + + case PDOORSND_Closing: + StopSound( SND_CHANNEL_BODY ); + StartSound( "snd_opening", SND_CHANNEL_BODY ); + break; + + case PDOORSND_Closed: + StopSound( SND_CHANNEL_BODY2 ); + StopSound( SND_CHANNEL_BODY ); + StartSound( "snd_closed", SND_CHANNEL_BODY2 ); + break; + + case PDOORSND_Opened: + StopSound( SND_CHANNEL_BODY2 ); + StartSound( "snd_opened", SND_CHANNEL_BODY2 ); + break; + + case PDOORSND_Stopped: + StopSound( SND_CHANNEL_BODY ); + break; + + case PDOORSND_FullyOpened: + StopSound( SND_CHANNEL_BODY ); + StopSound( SND_CHANNEL_BODY2 ); + StartSound( "snd_fullyopened", SND_CHANNEL_BODY2 ); + break; + } + doorSndState = newState; +} + +//-------------------------------- +// hhProxDoor::OpenPortal +//-------------------------------- +void hhProxDoor::OpenPortal( void ) { + if( areaPortal && !gameLocal.isClient ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + } + SetAASAreaState( false ); +} + +//-------------------------------- +// hhProxDoor::ClosePortal +//-------------------------------- +void hhProxDoor::ClosePortal( void ) { + if( areaPortal && !gameLocal.isClient ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_VIEW ); + } + SetAASAreaState( IsLocked() ); +} + +//-------------------------------- +// hhProxDoor::SetAASAreaState +//-------------------------------- +void hhProxDoor::SetAASAreaState( bool closed ) { + aas_area_closed = closed; + if( !openForMonsters ) { + aas_area_closed = true; + } + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL | AREACONTENTS_OBSTACLE, aas_area_closed ); +} + +//-------------------------------- +// hhProxDoor::Event_PollForExit +//-------------------------------- +void hhProxDoor::Event_PollForExit() { + if( PollClosestEntity() == -1.f ) { + SetDoorState( PROXSTATE_GoingInactive ); + return; + } + CancelEvents( &EV_PollForExit ); + PostEventMS( &EV_PollForExit, proxDoorRefreshMS ); +} + +//-------------------------------- +// hhProxDoor::PollClosestEntity +//-------------------------------- +float hhProxDoor::PollClosestEntity() { + int num; + int i; + idEntity* ents[MAX_GENTITIES]; + float bestLen; + float distSq; + + if (!doorTrigger) { + return 0.0f; + } + + num = gameLocal.clip.EntitiesTouchingBounds( doorTrigger->GetAbsBounds().Expand( pm_bboxwidth.GetFloat() ), MASK_SHOT_BOUNDINGBOX, ents, MAX_GENTITIES ); + + float thinkDistanceSq = (maxDistance + pm_bboxwidth.GetFloat()) * (maxDistance + pm_bboxwidth.GetFloat()); + bestLen = thinkDistanceSq; + idEntity* bestEnt = NULL; + + for( i = 0; i < num; i++ ) { + if( ents[ i ] && ents[ i ] != this && ents[ i ]->fl.touchTriggers && !ents[ i ]->IsType(hhProxDoorSection::Type) ) { + if( ents[ i ]->IsType(hhPlayer::Type) || ents[ i ]->IsType(hhSpiritProxy::Type) || (openForMonsters && ents[ i ]->IsType(idAI::Type) && ents[ i ]->GetHealth() > 0 ) ) { + if (ents[ i ]->IsType(hhPlayer::Type) && !fl.allowSpiritWalkTouch) { //rww - we don't want spirits to open proxy doors. + hhPlayer *pl = static_cast(ents[i]); + if (pl->IsSpiritWalking()) { + continue; + } + } + distSq = ( ents[ i ]->GetOrigin() - GetOrigin() ).LengthSqr(); + if( distSq < bestLen ) { + bestLen = distSq; + bestEnt = ents[ i ]; + } + } + } + } + if( bestLen < thinkDistanceSq ) { + entDistanceSq = bestLen; + } + else { + entDistanceSq = -1.f; + } + return entDistanceSq; +} + +//-------------------------------- +// hhProxDoor::SetDoorState +//-------------------------------- +void hhProxDoor::SetDoorState( EProxState doorState ) { + int i; + + //HUMANHEAD PCF mdl 04/27/06 - Don't allow locked doors to become active + if ( doorLocked && doorState == PROXSTATE_Active ) { + return; + } + + switch( doorState ) { + case PROXSTATE_Active: + BecomeActive( TH_TICKER ); + CancelEvents( &EV_PollForExit ); + PostEventMS( &EV_PollForExit, 500 ); + OpenPortal(); + break; + + case PROXSTATE_GoingInactive: + break; + + case PROXSTATE_Inactive: + // Guarantee the door is closed + for( i = 0; i < doorPieces.Num(); i++ ) { + if (doorPieces[i].IsValid()) { + doorPieces[ i ]->SetProximity( 0.0 ); + } + } + ClosePortal(); + CancelEvents( &EV_PollForExit ); + BecomeInactive( TH_TICKER ); + break; + } + + proxState = doorState; +} + +//-------------------------------- +// hhProxDoor::IsLocked +//-------------------------------- +bool hhProxDoor::IsLocked() { + return doorLocked; +} + +//-------------------------------- +// hhProxDoor::Lock +//-------------------------------- +void hhProxDoor::Lock( int f ) { + doorLocked = f > 0 ? true : false; + //HUMANHEAD PCF mdl 04/27/06 - Changed PROXSTATE_Inactive to PROXSTATE_GoingInactive + SetDoorState( doorLocked ? PROXSTATE_GoingInactive : PROXSTATE_Active ); + SetShaderParm( SHADERPARM_MODE, GetDoorShaderParm( doorLocked, false ) ); // 2=locked, 1=unlocked, 0=never locked + StopSound( SND_CHANNEL_ANY ); +} + +//-------------------------------- +// hhProxDoor::CrushEntities +//-------------------------------- +void hhProxDoor::CrushEntities() { + int num; + int i; + idEntity* ents[MAX_GENTITIES]; + + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX|CONTENTS_RENDERMODEL, ents, MAX_GENTITIES ); + + const char *damageType = spawnArgs.GetString("damageType"); + for( i = 0; i < num; i++ ) { + if( ents[ i ] != this && !ents[ i ]->IsType(hhProxDoorSection::Type) && !ents[ i ]->IsType(hhPlayer::Type) && !ents[ i ]->IsType(idAFAttachment::Type) ) { // Check for the player and idAFAttachment (head) in the case of spirit walk + ents[ i ]->Damage( this, this, vec3_origin, damageType, damage, INVALID_JOINT ); + ents[ i ]->SquishedByDoor( this ); + } + } +} + +//-------------------------------- +// hhProxDoor::Event_Touch +//-------------------------------- +void hhProxDoor::Event_Touch( idEntity *other, trace_t *trace ) { + if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { + if (other && other->IsType(hhPlayer::Type) && IsLocked() && gameLocal.time > nextSndTriggerTime) { + StartSound("snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + return; + } + if( proxState == PROXSTATE_Active ) { + return; + } + + if ( !other ) { + gameLocal.Warning("hhProxDoor: Event_Touch given NULL for other\n"); + return; + } + + float dist = ( other->GetOrigin() - GetOrigin() ).Length(); + if (dist > movementDistance) { + return; + } + + if( !IsLocked() ) { + SetDoorState( PROXSTATE_Active ); + } +} + +//-------------------------------- +// hhProxDoor::Event_Activate +//-------------------------------- +void hhProxDoor::Event_Activate( idEntity* activator ) { + if( spawnArgs.GetBool("locktrigger") ) { + Lock(!doorLocked); + SetAASAreaState( doorLocked ); + } +} + + +//------------------------------------------------------------------------------------------------- +// hhProxDoorTranslator. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( hhProxDoorSection, hhProxDoorTranslator ) +END_CLASS + +//-------------------------------- +// hhProxDoorTranslator::Spawn +//-------------------------------- +void hhProxDoorTranslator::Spawn( void ) { + idVec3 sectionOffset; + + sectionType = PROXSECTION_Translator; + + if( !spawnArgs.GetVector("section_offset", "0 0 0", sectionOffset) ) { + common->Warning( "No section offset found for '%s'", this->GetEntityDefName() ); + } + targetOrigin = sectionOffset; + baseOrigin = GetOrigin(); + + fl.networkSync = true; +} + +//-------------------------------- +// hhProxDoorTranslator::Save +//-------------------------------- +void hhProxDoorTranslator::Save(idSaveGame *savefile) const { + savefile->WriteVec3( baseOrigin ); + savefile->WriteVec3( targetOrigin ); +} + +//-------------------------------- +// hhProxDoorTranslator::Restore +//-------------------------------- +void hhProxDoorTranslator::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( baseOrigin ); + savefile->ReadVec3( targetOrigin ); +} + +//-------------------------------- +// hhProxDoorTranslator::ClientPredictionThink +//-------------------------------- +void hhProxDoorTranslator::ClientPredictionThink( void ) { + hhProxDoorSection::ClientPredictionThink(); +} + +//-------------------------------- +// hhProxDoorTranslator::WriteToSnapshot +//-------------------------------- +void hhProxDoorTranslator::WriteToSnapshot( idBitMsgDelta &msg ) const { + //hhProxDoorSection::WriteToSnapshot(msg); +#ifdef _SYNC_PROXDOORS + msg.WriteFloat(baseOrigin.x); + msg.WriteFloat(baseOrigin.y); + msg.WriteFloat(baseOrigin.z); + msg.WriteFloat(targetOrigin.x); + msg.WriteFloat(targetOrigin.y); + msg.WriteFloat(targetOrigin.z); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + idCQuat q = state->axis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + } + + msg.WriteFloat(proximity); +#else + WriteBindToSnapshot(msg); + msg.WriteBits(proxyParent.GetSpawnId(), 32); +#endif +} + +//-------------------------------- +// hhProxDoorTranslator::ReadFromSnapshot +//-------------------------------- +void hhProxDoorTranslator::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //hhProxDoorSection::ReadFromSnapshot(msg); +#ifdef _SYNC_PROXDOORS + baseOrigin.x = msg.ReadFloat(); + baseOrigin.y = msg.ReadFloat(); + baseOrigin.z = msg.ReadFloat(); + targetOrigin.x = msg.ReadFloat(); + targetOrigin.y = msg.ReadFloat(); + targetOrigin.z = msg.ReadFloat(); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + state->axis = q.ToMat3(); + } + + float prox = msg.ReadFloat(); + SetProximity(prox); +#else + ReadBindFromSnapshot(msg); + proxyParent.SetSpawnId(msg.ReadBits(32)); + if (proxyParent.IsValid() && proxyParent->IsType(hhProxDoor::Type)) { + hhProxDoor *parentPtr = static_cast(proxyParent.GetEntity()); + + if (parentPtr->hasNetData) { + if (!hasNetData) { + idVec3 parentOrigin; + idMat3 parentAxis; + + parentOrigin = proxyParent->GetOrigin();//proxyParent->spawnArgs.GetVector("origin", "0 0 0", parentOrigin); + parentAxis = proxyParent->GetAxis();//proxyParent->spawnArgs.GetMatrix("rotation", "1 0 0 0 1 0 0 0 1", parentAxis); + + SetOrigin(parentOrigin); + SetAxis(parentAxis); + + baseOrigin = GetOrigin(); + + spawnArgs.SetVector("origin", parentOrigin); + spawnArgs.SetMatrix("rotation", parentAxis); + + hasNetData = true; + } + } + else { + hasNetData = false; + } + } +#endif +} + +//-------------------------------- +// hhProxDoorTranslator::SetProximity +//-------------------------------- +void hhProxDoorTranslator::SetProximity( float prox ) { + SetOrigin( baseOrigin + (targetOrigin * prox) ); + proximity = prox; +} + + +//------------------------------------------------------------------------------------------------- +// hhProxDoorRotator. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( hhProxDoorSection, hhProxDoorRotator ) + EVENT( EV_PostSpawn, hhProxDoorRotator::Event_PostSpawn ) +END_CLASS + +//-------------------------------- +// hhProxDoorRotator::Spawn +//-------------------------------- +void hhProxDoorRotator::Spawn( void ) { + sectionType = PROXSECTION_Rotator; + + if (gameLocal.isMultiplayer) { + if (!gameLocal.isClient) { + PostEventMS( &EV_PostSpawn, 0 ); + fl.networkSync = true; + } + } + else { //just call it now. some sp maps might target a door piece in the spawn function of an entity or something crazy like that. + Event_PostSpawn(); + } +} + +void hhProxDoorRotator::Event_PostSpawn( void ) { + idVec3 sectionOffset; + idVec3 rotVector; + float rotAngle; + + if( !spawnArgs.GetVector("section_offset", "0 0 0", sectionOffset) ) { + common->Warning( "No section offset found for '%s'", this->GetEntityDefName() ); + } + if( !spawnArgs.GetVector("rot_vector", "0 0 0", rotVector) ) { + common->Warning( "No rotation vector found for '%s'", this->GetEntityDefName() ); + } + if( !spawnArgs.GetFloat("rot_angle", "0 0 0", rotAngle) ) { + common->Warning( "No rotation angle found for '%s'", this->GetEntityDefName() ); + } + + idDict args; + args.SetVector( "origin", GetOrigin() + (sectionOffset * GetAxis()) ); + args.SetMatrix( "rotation", GetAxis() ); + args.SetVector( "rot_vector", rotVector ); + args.SetFloat( "rot_angle", rotAngle ); + bindParent = static_cast( gameLocal.SpawnEntityType(hhProxDoorRotMaster::Type, &args) ); + if( !bindParent.IsValid() ) { + common->Warning( "Failed to spawn bindParent for '%s'", GetEntityDefName() ); + } + else { + bindParent->proxyParent = this; + } + Bind( bindParent.GetEntity(), true ); +} + +//-------------------------------- +// hhProxDoorRotator::Save +//-------------------------------- +void hhProxDoorRotator::Save(idSaveGame *savefile) const { + bindParent.Save(savefile); +} + +//-------------------------------- +// hhProxDoorRotator::Restore +//-------------------------------- +void hhProxDoorRotator::Restore( idRestoreGame *savefile ) { + bindParent.Restore(savefile); +} + +//-------------------------------- +// hhProxDoorRotator::ClientPredictionThink +//-------------------------------- +void hhProxDoorRotator::ClientPredictionThink( void ) { + hhProxDoorSection::ClientPredictionThink(); +} + +//-------------------------------- +// hhProxDoorRotator::WriteToSnapshot +//-------------------------------- +void hhProxDoorRotator::WriteToSnapshot( idBitMsgDelta &msg ) const { +// hhProxDoorSection::WriteToSnapshot(msg); +#ifdef _SYNC_PROXDOORS + WriteBindToSnapshot(msg); + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + + msg.WriteFloat(state->origin.x); + msg.WriteFloat(state->origin.y); + msg.WriteFloat(state->origin.z); + msg.WriteFloat(state->localOrigin.x); + msg.WriteFloat(state->localOrigin.y); + msg.WriteFloat(state->localOrigin.z); + idCQuat q = state->localAxis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + } +#else + msg.WriteBits(proxyParent.GetSpawnId(), 32); + WriteBindToSnapshot(msg); + msg.WriteBits(bindParent.GetSpawnId(), 32); +#endif + //not needed without prediction + //msg.WriteBits(bindParent.GetSpawnId(), 32); +} + +//-------------------------------- +// hhProxDoorRotator::ReadFromSnapshot +//-------------------------------- +void hhProxDoorRotator::ReadFromSnapshot( const idBitMsgDelta &msg ) { +// hhProxDoorSection::ReadFromSnapshot(msg); +#ifdef _SYNC_PROXDOORS + ReadBindFromSnapshot(msg); + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + + state->origin.x = msg.ReadFloat(); + state->origin.y = msg.ReadFloat(); + state->origin.z = msg.ReadFloat(); + state->localOrigin.x = msg.ReadFloat(); + state->localOrigin.y = msg.ReadFloat(); + state->localOrigin.z = msg.ReadFloat(); + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + state->localAxis = q.ToMat3(); + } +#else + proxyParent.SetSpawnId(msg.ReadBits(32)); + if (proxyParent.IsValid() && proxyParent->IsType(hhProxDoor::Type)) { + hhProxDoor *parentPtr = static_cast(proxyParent.GetEntity()); + + if (parentPtr->hasNetData) { + if (!hasNetData) { + idVec3 parentOrigin; + idMat3 parentAxis; + + parentOrigin = proxyParent->GetOrigin();//proxyParent->spawnArgs.GetVector("origin", "0 0 0", parentOrigin); + parentAxis = proxyParent->GetAxis();//proxyParent->spawnArgs.GetMatrix("rotation", "1 0 0 0 1 0 0 0 1", parentAxis); + + SetOrigin(parentOrigin); + SetAxis(parentAxis); + + spawnArgs.SetVector("origin", parentOrigin); + spawnArgs.SetMatrix("rotation", parentAxis); + + hasNetData = true; + } + } + else { + hasNetData = false; + } + } + ReadBindFromSnapshot(msg); + bindParent.SetSpawnId(msg.ReadBits(32)); +#endif + //not needed without prediction + /* + int spawnId = msg.ReadBits(32); + if (!spawnId) { + bindParent = NULL; + } + else { + bindParent.SetSpawnId(spawnId); + } + */ +} + +//-------------------------------- +// hhProxDoorRotator::SetProximity +//-------------------------------- +void hhProxDoorRotator::SetProximity( float prox ) { + if( bindParent.IsValid() ) { + bindParent->SetProximity( prox ); + } + proximity = prox; +} + +//------------------------------------------------------------------------------------------------- +// hhProxDoorRotMaster. +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( hhProxDoorSection, hhProxDoorRotMaster ) +END_CLASS + +//-------------------------------- +// hhProxDoorRotMaster::Spawn +//-------------------------------- +void hhProxDoorRotMaster::Spawn( void ) { + idVec3 rot_vector; + spawnArgs.GetVector("rot_vector", "0 0 1", rot_vector); + baseAxis = GetAxis(); + rotVector = GetAxis() * rot_vector; + + spawnArgs.GetFloat("rot_angle", "0.0", rotAngle); + + fl.networkSync = true; +} + +//-------------------------------- +// hhProxDoorRotMaster::Save +//-------------------------------- +void hhProxDoorRotMaster::Save(idSaveGame *savefile) const { + savefile->WriteVec3( rotVector ); + savefile->WriteFloat( rotAngle ); + savefile->WriteMat3( baseAxis ); +} + +//-------------------------------- +// hhProxDoorRotMaster::Restore +//-------------------------------- +void hhProxDoorRotMaster::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( rotVector ); + savefile->ReadFloat( rotAngle ); + savefile->ReadMat3( baseAxis ); +} + +//-------------------------------- +// hhProxDoorRotMaster::ClientPredictionThink +//-------------------------------- +void hhProxDoorRotMaster::ClientPredictionThink( void ) { + hhProxDoorSection::ClientPredictionThink(); +} + +//-------------------------------- +// hhProxDoorRotMaster::WriteToSnapshot +//-------------------------------- +void hhProxDoorRotMaster::WriteToSnapshot( idBitMsgDelta &msg ) const { + //hhProxDoorSection::WriteToSnapshot(msg); +#ifdef _SYNC_PROXDOORS + msg.WriteFloat(rotVector.x); + msg.WriteFloat(rotVector.y); + msg.WriteFloat(rotVector.z); + + msg.WriteFloat(rotAngle); + + idCQuat q = baseAxis.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + msg.WriteFloat(state->origin.x); + msg.WriteFloat(state->origin.y); + msg.WriteFloat(state->origin.z); + } + msg.WriteFloat(proximity); +#else + WriteBindToSnapshot(msg); + msg.WriteBits(proxyParent.GetSpawnId(), 32); +#endif +} + +//-------------------------------- +// hhProxDoorRotMaster::ReadFromSnapshot +//-------------------------------- +void hhProxDoorRotMaster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //hhProxDoorSection::ReadFromSnapshot(msg); +#ifdef _SYNC_PROXDOORS + rotVector.x = msg.ReadFloat(); + rotVector.y = msg.ReadFloat(); + rotVector.z = msg.ReadFloat(); + + rotAngle = msg.ReadFloat(); + + idCQuat q; + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + baseAxis = q.ToMat3(); + + if (GetPhysics()->IsType(idPhysics_Static::Type)) { + idPhysics_Static *phys = static_cast(GetPhysics()); + staticPState_t *state = phys->GetPState(); + state->origin.x = msg.ReadFloat(); + state->origin.y = msg.ReadFloat(); + state->origin.z = msg.ReadFloat(); + } + float prox = msg.ReadFloat(); + SetProximity(prox); +#else + ReadBindFromSnapshot(msg); + proxyParent.SetSpawnId(msg.ReadBits(32)); + if (proxyParent.IsValid() && proxyParent->IsType(hhProxDoorRotator::Type)) { + hhProxDoorRotator *parentPtr = static_cast(proxyParent.GetEntity()); + if (parentPtr->hasNetData) { + if (!hasNetData) { + idVec3 sectionOffset, rot_vector; + idVec3 parentOrigin; + idMat3 parentAxis; + + proxyParent->spawnArgs.GetVector("origin", "0 0 0", parentOrigin); + proxyParent->spawnArgs.GetMatrix("rotation", "1 0 0 0 1 0 0 0 1", parentAxis); + + proxyParent->spawnArgs.GetVector("section_offset", "0 0 0", sectionOffset); + proxyParent->spawnArgs.GetVector("rot_vector", "0 0 0", rot_vector); + proxyParent->spawnArgs.GetFloat("rot_angle", "0.0", rotAngle); + SetOrigin(parentOrigin+(sectionOffset*parentAxis)); + SetAxis(parentAxis); + + baseAxis = GetAxis(); + rotVector = GetAxis() * rot_vector; + + hasNetData = true; + } + } + else { + parentPtr->hasNetData = false; + } + } +#endif +} + +//-------------------------------- +// hhProxDoorRotMaster::SetProximity +//-------------------------------- +void hhProxDoorRotMaster::SetProximity( float prox ) { + idRotation desRotation( vec3_origin, rotVector, prox*rotAngle ); + SetAxis( baseAxis * desRotation.ToMat3() ); + + //rww - proxy door pieces keep getting stuck, so make sure this bastard keeps running physics + if (!IsActive(TH_PHYSICS)) { + BecomeActive(TH_PHYSICS); + } + proximity = prox; +} diff --git a/neo/prey/Prey/game_proxdoor.h b/neo/prey/Prey/game_proxdoor.h new file mode 100644 index 00000000..fe475d44 --- /dev/null +++ b/neo/prey/Prey/game_proxdoor.h @@ -0,0 +1,204 @@ +#ifndef __PREY_PROXDOOR_H__ +#define __PREY_PROXDOOR_H__ + +class hhProxDoorSection; +typedef idEntityPtr doorSectionPtr_t; //rww - so that the list of door sections can be safe. + +/*********************************************************************** + hhProxDoor. + The main door control. Has a model associated with it (the frame). +***********************************************************************/ +class hhProxDoor : public idEntity { + CLASS_PROTOTYPE( hhProxDoor ); + + public: + hhProxDoor(); + virtual ~hhProxDoor(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SpawnSoundTrigger(); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Ticker( void ); + + //Events. + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_PollForExit( void ); + virtual void Event_PostSpawn( void ); + void Event_Activate( idEntity* activator ); + + //Portal Control. + void OpenPortal( void ); + void ClosePortal( void ); + + //AAS Control. + void SetAASAreaState( bool closed ); + + //Lock Control + bool IsLocked( void ); + void Lock( int f ); + + bool hasNetData; //rww - net stuff + +protected: + enum EProxState { + PROXSTATE_Active, + PROXSTATE_Inactive, + PROXSTATE_GoingInactive, + }; + + enum EPDoorSound { + PDOORSND_Opening, + PDOORSND_Closing, + PDOORSND_Closed, + PDOORSND_Opened, + PDOORSND_FullyOpened, + PDOORSND_Stopped, + }; + + void SetDoorState( EProxState doorState ); + void UpdateSoundState( EPDoorSound newState ); + float PollClosestEntity(); + void CrushEntities(); + +protected: + EProxState proxState; //our state + EPDoorSound doorSndState; //state of our door sound + float lastAmount; //last amount [mainly used for sound] + idList doorPieces; //all of the pieces that make up the door + idClipModel* doorTrigger; //trigger that wakes us up + idClipModel* sndTrigger; //trigger for playing locked sound + int nextSndTriggerTime; // next time to play door locked sound + float entDistanceSq; //squared distance of the closest entity + float maxDistance; //distance of trigger + float movementDistance; //distance to start movement at + float lastDistance; //previous distance + float stopDistance; //distance to stop movement at + float damage; // Amount of crush damage we do + qhandle_t areaPortal; // 0 = no portal, > 0 = our portal we are by + bool doorLocked; + bool openForMonsters; + bool aas_area_closed; + +}; + +/*********************************************************************** + hhProxDoorSection. + Base class for all of the pieces used to make up a hhProxDoor. +***********************************************************************/ +class hhProxDoorSection : public idEntity { + ABSTRACT_PROTOTYPE( hhProxDoorSection ); + +public: + void Spawn( void ); + + void Save( idSaveGame *savefile ) const { savefile->WriteInt(sectionType); savefile->WriteFloat( proximity ); } + void Restore( idRestoreGame *savefile ) { savefile->ReadInt((int&)sectionType); savefile->ReadFloat( proximity ); } + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ) = 0; + + idEntityPtr proxyParent; //rww - for horrible network hacks + + bool hasNetData; //rww - net stuff + + float proximity; +protected: + enum EProxSection { + PROXSECTION_None, + PROXSECTION_Rotator, + PROXSECTION_Translator, + PROXSECTION_RotatorMaster, + }; + + EProxSection sectionType; +}; + +/*********************************************************************** + hhProxDoorRotator. + A rotating door piece. Uses an offset DoorRotMaster to bind to. +***********************************************************************/ +class hhProxDoorRotator : public hhProxDoorSection { + CLASS_PROTOTYPE( hhProxDoorRotator ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ); + + virtual void Event_PostSpawn( void ); + +protected: + idEntityPtr bindParent; +}; + +/*********************************************************************** + hhProxDoorTranslator. + A translating door piece. +***********************************************************************/ +class hhProxDoorTranslator : public hhProxDoorSection { + CLASS_PROTOTYPE( hhProxDoorTranslator ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ); + +protected: + idVec3 baseOrigin; + idVec3 targetOrigin; + + +}; + +/*********************************************************************** + hhProxDoorRotMaster + The master that DoorRotators bind to. +***********************************************************************/ +class hhProxDoorRotMaster : public hhProxDoorSection { + CLASS_PROTOTYPE( hhProxDoorRotMaster ); + +public: + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - netcode + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void SetProximity( float prox ); + +protected: + idVec3 rotVector; + float rotAngle; + idMat3 baseAxis; +}; + + +#endif //__PREY_PROXDOOR_H__ \ No newline at end of file diff --git a/neo/prey/Prey/game_rail.cpp b/neo/prey/Prey/game_rail.cpp new file mode 100644 index 00000000..8d632ee6 --- /dev/null +++ b/neo/prey/Prey/game_rail.cpp @@ -0,0 +1,85 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* TODO: + could optionally allow multiple bindcontroller bones, for coop +*/ + +//========================================================================== +// +// hhRailRide +// +//========================================================================== + +CLASS_DECLARATION(hhAnimated, hhRailRide) + EVENT( EV_Activate, hhRailRide::Event_Activate ) + EVENT( EV_BindAttach, hhRailRide::Event_Attach ) + EVENT( EV_BindDetach, hhRailRide::Event_Detach ) +END_CLASS + +void hhRailRide::Spawn() { + fl.takedamage = spawnArgs.GetBool("dropOnPain"); + float tension = spawnArgs.GetFloat("tension", "0.01"); + float yawLimit = spawnArgs.GetFloat("yawlimit", "180"); + float pitchLimit = spawnArgs.GetFloat("pitchlimit", "0.0"); + const char *handName = spawnArgs.GetString("def_hand"); + const char *animName = spawnArgs.GetString("boundanim"); + + // spawn a bind controller at railbone + const char *bonename = spawnArgs.GetString("railbone"); + bindController = static_cast( gameLocal.SpawnObject(spawnArgs.GetString("def_bindController")) ); + assert(bindController); + bindController->MoveToJoint(this, bonename); + bindController->BindToJoint(this, bonename, true); + bindController->SetTension(tension); + bindController->SetRiderParameters(animName, handName, yawLimit, pitchLimit); +} + +void hhRailRide::Save(idSaveGame *savefile) const { + savefile->WriteObject( bindController ); +} + +void hhRailRide::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( bindController ) ); +} + +bool hhRailRide::Pain(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + bindController->Detach(); + + return( true ); +} + +void hhRailRide::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bindController->Detach(); +} + +bool hhRailRide::ValidRider(idEntity *entity) { + return (entity && entity->spawnArgs.GetBool("RailAttachable")); +} + +void hhRailRide::Attach(idEntity *rider, bool loose) { + if (ValidRider(rider)) { + bindController->Attach(rider, loose); + } +} + +void hhRailRide::Detach() { + bindController->Detach(); +} + +void hhRailRide::Event_Activate( idEntity *activator ) { + Attach(activator, false); + hhAnimated::Event_Activate(activator); // allow animation to start +} + +void hhRailRide::Event_Attach(idEntity *rider, bool loose) { + Attach(rider, loose); +} + +void hhRailRide::Event_Detach() { + Detach(); +} + + diff --git a/neo/prey/Prey/game_rail.h b/neo/prey/Prey/game_rail.h new file mode 100644 index 00000000..a1c144d1 --- /dev/null +++ b/neo/prey/Prey/game_rail.h @@ -0,0 +1,41 @@ + +#ifndef __HH_GAME_RAIL_H__ +#define __HH_GAME_RAIL_H__ + +class hhBindController; + +/* +=================================================================================== + + hhRailRide + + An animating object that controls player position and orientation. + Player can look around within some restricted cone of vision. + +=================================================================================== +*/ + +class hhRailRide : public hhAnimated { + +public: + CLASS_PROTOTYPE( hhRailRide ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + 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 ); + void Attach(idEntity *rider, bool loose); + void Detach(); + bool ValidRider(idEntity *entity); + +protected: + void Event_Activate( idEntity *activator ); + void Event_Attach(idEntity *rider, bool loose); + void Event_Detach(); + + hhBindController * bindController; +}; + + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_railshuttle.cpp b/neo/prey/Prey/game_railshuttle.cpp new file mode 100644 index 00000000..13f8e631 --- /dev/null +++ b/neo/prey/Prey/game_railshuttle.cpp @@ -0,0 +1,620 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhRailShuttle +// +//========================================================================== + +#define VCOCKPIT_PARM_POWER 4 +#define VCOCKPIT_PARM_FIRE 5 +#define VCOCKPIT_PARM_JUMP 6 +#define VCOCKPIT_PARM_MOVE 7 + +const idEventDef EV_SetOrbitRadius("setOrbitRadius", "fd"); +const idEventDef EV_RailShuttle_Jump("railShuttleJump"); +const idEventDef EV_RailShuttle_FinalJump("railShuttleFinalJump"); +const idEventDef EV_RailShuttle_IsJumping("railShuttleIsJumping", NULL, 'f'); +const idEventDef EV_RailShuttle_Disengage("railShuttleDisengage"); +const idEventDef EV_HideArcs(""); +const idEventDef EV_RestorePower(""); +const idEventDef EV_GetCockpit( "getCockpit", "", 'e' ); //rdr + +CLASS_DECLARATION(hhVehicle, hhRailShuttle) + EVENT( EV_SetOrbitRadius, hhRailShuttle::Event_SetOrbitRadius ) + EVENT( EV_PostSpawn, hhRailShuttle::Event_PostSpawn ) + EVENT( EV_RailShuttle_Jump, hhRailShuttle::Event_Jump ) + EVENT( EV_RailShuttle_FinalJump, hhRailShuttle::Event_FinalJump ) + EVENT( EV_RailShuttle_IsJumping, hhRailShuttle::Event_IsJumping ) + EVENT( EV_RailShuttle_Disengage, hhRailShuttle::Event_Disengage ) + EVENT( EV_HideArcs, hhRailShuttle::Event_HideArcs ) + EVENT( EV_RestorePower, hhRailShuttle::Event_RestorePower ) + EVENT( EV_GetCockpit, hhRailShuttle::Event_GetCockpit ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +void hhRailShuttle::Spawn() { + fl.networkSync = true; + + turretPitchLimit = spawnArgs.GetFloat("turretPitchLimit"); + turretYawLimit = spawnArgs.GetFloat("turretYawLimit"); + + defaultRadius = spawnArgs.GetFloat("sphereRadius", "1000"); + sphereRadius.Init(gameLocal.time, 0, defaultRadius, defaultRadius); + + sphereCenter = spawnArgs.GetVector("sphereCenter"); + sphereAngles = ang_zero; + sphereVelocity = ang_zero; + spherePitchMin = spawnArgs.GetFloat("spherePitchMin"); + spherePitchMax = spawnArgs.GetFloat("spherePitchMax"); + + SetPhysics( NULL ); // No collision, no expensive shuttle physics + GetPhysics()->SetContents(0); + + linearFriction = spawnArgs.GetFloat("friction_linear"); + terminalVelocity = spawnArgs.GetFloat("terminalVelocity"); + + bJumping = false; + bDisengaged = false; + jumpStartTime = 0; + jumpInitSpeed = spawnArgs.GetFloat("jumpInitSpeed"); + jumpAccel = spawnArgs.GetFloat("jumpAccel"); + jumpAccelTime = spawnArgs.GetFloat("jumpAccelTime"); + jumpReturnForce = spawnArgs.GetFloat("jumpReturnForce"); + jumpBounceCount = spawnArgs.GetInt("jumpBounceCount"); + jumpBounceFactor = spawnArgs.GetFloat("jumpBounceFactor"); + jumpThrustMovementScale = spawnArgs.GetFloat("jumpThrustMovementScale"); + jumpBounceMovementScale = spawnArgs.GetFloat("jumpBounceMovementScale"); + bWallMovementSoundPlaying = false; + bAirMovementSoundPlaying = false; + bBounced = false; + + localViewAngles = ang_zero; + turret = NULL; + canopy = NULL; + leftArc = NULL; + rightArc = NULL; + + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + bUpdateViewAngles = false; + + PostEventMS(&EV_PostSpawn, 0); +} + +void hhRailShuttle::Event_PostSpawn() { + // Spawn the turret + turret = gameLocal.SpawnObject(spawnArgs.GetString("def_turret")); + if (turret.IsValid()) { + turret->SetOrigin( GetOrigin() + spawnArgs.GetVector("offset_turret") * GetAxis()); + turret->SetAxis(GetAxis()); + turret->Bind(this, false); + turret->GetPhysics()->SetContents(0); + turret->Hide(); + } + // Spawn the canopy + canopy = gameLocal.SpawnObject(spawnArgs.GetString("def_turret")); + if (canopy.IsValid()) { + canopy->SetOrigin( GetOrigin() + spawnArgs.GetVector("offset_turret") * GetAxis()); + canopy->SetAxis(GetAxis()); + canopy->Bind(this, true); + canopy->GetPhysics()->SetContents(0); //temp: need to make it turn solid after player enters + canopy->Hide(); + } + + // Spawn the arc beams + idVec3 leftPos1 = GetOrigin() + spawnArgs.GetVector("offset_leftArcStart") * GetAxis(); + idVec3 leftPos2 = GetOrigin() + spawnArgs.GetVector("offset_leftArcEnd") * GetAxis(); + idVec3 leftDir = leftPos2 - leftPos1; + leftDir.Normalize(); + leftArc = hhBeamSystem::SpawnBeam(leftPos1, spawnArgs.GetString("beam_arc"), mat3_identity, true); + HH_ASSERT( leftArc.IsValid() ); + leftArc->SetOrigin(leftPos1); + leftArc->SetAxis(leftDir.ToMat3()); + leftArc->Bind(turret.GetEntity(), true); + leftArc->Activate( false ); + leftArc->fl.neverDormant = true; + leftArc->ToggleBeamLength(true); + leftArc->SetTargetLocation( leftPos2 ); + + idVec3 rightPos1 = GetOrigin() + spawnArgs.GetVector("offset_rightArcStart") * GetAxis(); + idVec3 rightPos2 = GetOrigin() + spawnArgs.GetVector("offset_rightArcEnd") * GetAxis(); + idVec3 rightDir = rightPos2 - rightPos1; + rightDir.Normalize(); + rightArc = hhBeamSystem::SpawnBeam(rightPos1, spawnArgs.GetString("beam_arc"), mat3_identity, true); + HH_ASSERT( rightArc.IsValid() ); + rightArc->SetOrigin(rightPos1); + rightArc->SetAxis(rightDir.ToMat3()); + rightArc->Bind(turret.GetEntity(), true); + rightArc->Activate( false ); + rightArc->fl.neverDormant = true; + rightArc->ToggleBeamLength(true); + rightArc->SetTargetLocation( rightPos2 ); +} + +void hhRailShuttle::Save(idSaveGame *savefile) const { + savefile->WriteFloat( defaultRadius ); + + savefile->WriteFloat( sphereRadius.GetStartTime() ); // idInterpolate + savefile->WriteFloat( sphereRadius.GetDuration() ); + savefile->WriteFloat( sphereRadius.GetStartValue() ); + savefile->WriteFloat( sphereRadius.GetEndValue() ); + + savefile->WriteAngles( sphereAngles ); + savefile->WriteAngles( sphereVelocity ); + savefile->WriteAngles( sphereAcceleration ); + savefile->WriteVec3( sphereCenter ); + savefile->WriteFloat( spherePitchMin ); + savefile->WriteFloat( spherePitchMax ); + savefile->WriteFloat( turretYawLimit ); + savefile->WriteFloat( turretPitchLimit ); + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( terminalVelocity ); + savefile->WriteAngles( localViewAngles ); + savefile->WriteAngles( oldViewAngles ); + + turret.Save( savefile ); + canopy.Save( savefile ); + leftArc.Save( savefile ); + rightArc.Save( savefile ); + + savefile->WriteBool( bDisengaged ); + savefile->WriteBool( bJumping ); + savefile->WriteBool( bBounced ); + savefile->WriteInt( jumpStartTime ); + savefile->WriteInt( jumpStage ); + savefile->WriteFloat( jumpSpeed ); + savefile->WriteFloat( jumpPosition ); + savefile->WriteInt( jumpNumBounces ); + savefile->WriteFloat( jumpInitSpeed ); + savefile->WriteFloat( jumpAccel ); + savefile->WriteFloat( jumpAccelTime ); + savefile->WriteFloat( jumpReturnForce ); + savefile->WriteInt( jumpBounceCount ); + savefile->WriteFloat( jumpBounceFactor ); + savefile->WriteFloat( jumpThrustMovementScale ); + savefile->WriteFloat( jumpBounceMovementScale ); + savefile->WriteBool( bWallMovementSoundPlaying ); + savefile->WriteBool( bAirMovementSoundPlaying ); +} + +void hhRailShuttle::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( defaultRadius ); + + float set; + savefile->ReadFloat( set ); // idInterpolate + sphereRadius.SetStartTime( set ); + savefile->ReadFloat( set ); + sphereRadius.SetDuration( set ); + savefile->ReadFloat( set ); + sphereRadius.SetStartValue( set ); + savefile->ReadFloat( set ); + sphereRadius.SetEndValue( set ); + + savefile->ReadAngles( sphereAngles ); + savefile->ReadAngles( sphereVelocity ); + savefile->ReadAngles( sphereAcceleration ); + savefile->ReadVec3( sphereCenter ); + savefile->ReadFloat( spherePitchMin ); + savefile->ReadFloat( spherePitchMax ); + savefile->ReadFloat( turretYawLimit ); + savefile->ReadFloat( turretPitchLimit ); + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( terminalVelocity ); + savefile->ReadAngles( localViewAngles ); + savefile->ReadAngles( oldViewAngles ); + + turret.Restore( savefile ); + canopy.Restore( savefile ); + leftArc.Restore( savefile ); + rightArc.Restore( savefile ); + + savefile->ReadBool( bDisengaged ); + savefile->ReadBool( bJumping ); + savefile->ReadBool( bBounced ); + savefile->ReadInt( jumpStartTime ); + savefile->ReadInt( jumpStage ); + savefile->ReadFloat( jumpSpeed ); + savefile->ReadFloat( jumpPosition ); + savefile->ReadInt( jumpNumBounces ); + savefile->ReadFloat( jumpInitSpeed ); + savefile->ReadFloat( jumpAccel ); + savefile->ReadFloat( jumpAccelTime ); + savefile->ReadFloat( jumpReturnForce ); + savefile->ReadInt( jumpBounceCount ); + savefile->ReadFloat( jumpBounceFactor ); + savefile->ReadFloat( jumpThrustMovementScale ); + savefile->ReadFloat( jumpBounceMovementScale ); + savefile->ReadBool( bWallMovementSoundPlaying ); + savefile->ReadBool( bAirMovementSoundPlaying ); + + SetPhysics( NULL ); // No collision, no expensive shuttle physics + GetPhysics()->SetContents(0); + + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + bUpdateViewAngles = true; +} + +void hhRailShuttle::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhVehicle::WriteToSnapshot(msg); +} + +void hhRailShuttle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhVehicle::ReadFromSnapshot(msg); +} + +void hhRailShuttle::ClientPredictionThink( void ) { + Think(); + + UpdateVisuals(); + Present(); +} + +void hhRailShuttle::BecomeConsole() { + if (turret.IsValid()) { + turret->Hide(); + } + if (canopy.IsValid()) { + canopy->Hide(); + } + if (leftArc.IsValid()) { + leftArc->Activate( false ); + } + if (rightArc.IsValid()) { + rightArc->Activate( false ); + } + hhVehicle::BecomeConsole(); +} + +void hhRailShuttle::BecomeVehicle() { + if (turret.IsValid()) { + turret->Show(); + } + if (canopy.IsValid()) { + canopy->Show(); + } + + hhVehicle::BecomeVehicle(); +} + +// Static function to determine if a pilot is suitable +bool hhRailShuttle::ValidPilot( idActor *act ) { + if (act && act->health > 0) { + if (act->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(act); + if( !player->IsSpiritOrDeathwalking() && !player->IsPossessed() ) { + return true; + } + } + else if (act->IsType(idAI::Type)) { + idAI *ai = static_cast(act); + if (ai->spawnArgs.GetBool("canPilotShuttle")) { + return true; + } + } + } + return false; +} + +bool hhRailShuttle::WillAcceptPilot( idActor *act ) { + return IsConsole() && hhRailShuttle::ValidPilot( act ); +} + +void hhRailShuttle::AcceptPilot( hhPilotVehicleInterface* pilotInterface ) { + hhVehicle::AcceptPilot(pilotInterface); + + // supress model in player views, but allow it in mirrors and remote views + if (turret.IsValid() && GetPilot()->IsType(hhPlayer::Type)) { + turret->GetRenderEntity()->suppressSurfaceInViewID = GetPilot()->entityNumber + 1; + } + + oldViewAngles = GetPilot()->viewAxis.ToAngles(); +} + +idVec3 hhRailShuttle::GetFireOrigin() { + return turret->GetOrigin(); +} + +idMat3 hhRailShuttle::GetFireAxis() { + return turret->GetAxis(); +} + +void hhRailShuttle::ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ) { + float movementScale; + + if ( cmds ) { + ProcessButtons( *cmds ); + + //Check vehicle here because we could have exited the vehicle in ProcessButtons + if( !IsVehicle() ) { + return; + } + + ProcessImpulses( *cmds ); + + // Apply booster to allow key taps to be very low thrust + thrustScale = idMath::ClampFloat( thrustMin, thrustMax, thrustScale * thrustAccel ); + + // Apply our forces as orbit velocity + if ((cmds->rightmove || cmds->upmove || cmds->forwardmove) && HasPower(1) && !bDisengaged) { + sphereAcceleration.yaw = cmds->rightmove * thrustFactor * thrustScale * (60.0f * USERCMD_ONE_OVER_HZ); + sphereAcceleration.pitch = (cmds->upmove ? cmds->upmove : cmds->forwardmove) * thrustFactor * thrustScale * (60.0f * USERCMD_ONE_OVER_HZ); + sphereAcceleration.roll = 0; + sphereAcceleration += -sphereVelocity * linearFriction * (60.0f * USERCMD_ONE_OVER_HZ); + SetCockpitParm( VCOCKPIT_PARM_MOVE, -MS2SEC( gameLocal.time ) ); + if (bJumping) { + PlayAirMovementSound(); + } + else { + PlayWallMovementSound(); + } + } + else { + sphereAcceleration = -sphereVelocity * linearFriction * (60.0f * USERCMD_ONE_OVER_HZ); + thrustScale = thrustMin; + if (bJumping) { + StopAirMovementSound(); + } + else { + StopWallMovementSound(); + } + } + + sphereVelocity += sphereAcceleration * (60.0f * USERCMD_ONE_OVER_HZ); + sphereVelocity.pitch = idMath::ClampFloat(-terminalVelocity, terminalVelocity, sphereVelocity.pitch); + sphereVelocity.yaw = idMath::ClampFloat(-terminalVelocity, terminalVelocity, sphereVelocity.yaw); + + movementScale = bJumping ? ( bBounced ? jumpBounceMovementScale : jumpThrustMovementScale ) : 1.f; + sphereAngles += sphereVelocity * movementScale * (60.0f * USERCMD_ONE_OVER_HZ); + if (sphereAngles.pitch <= spherePitchMin || sphereAngles.pitch >= spherePitchMax) { + sphereAngles.pitch = idMath::ClampFloat(spherePitchMin, spherePitchMax, sphereAngles.pitch); + sphereVelocity.pitch = 0; + } + sphereAngles = sphereAngles.Normalize180(); + + float radius = sphereRadius.GetCurrentValue(gameLocal.time) - UpdateJump( cmds ); + + idMat3 sphereAxis = sphereAngles.ToMat3(); + SetOrigin(sphereCenter - sphereAxis[0] * radius); + SetAxis(sphereAxis); + + memcpy( &oldCmds, cmds, sizeof(usercmd_t) ); + } + + // Apply viewAngles to turret + if ( viewAngles ) { + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + if ( bUpdateViewAngles ) { + // Update the old view angles after loading from a savegame + bUpdateViewAngles = false; + } else { + idAngles deltaViewAngles; + deltaViewAngles = *viewAngles - oldViewAngles; + localViewAngles += deltaViewAngles; + + // Clamp localViewAngles + if ( !IsNoClipping() ) { + localViewAngles.pitch = idMath::AngleNormalize180( localViewAngles.pitch ); + localViewAngles.pitch = idMath::ClampFloat(-turretPitchLimit, turretPitchLimit, localViewAngles.pitch); + localViewAngles.pitch = idMath::AngleNormalize180( localViewAngles.pitch ); + + localViewAngles.yaw = idMath::AngleNormalize180( localViewAngles.yaw ); + localViewAngles.yaw = idMath::ClampFloat(-turretYawLimit, turretYawLimit, localViewAngles.yaw); + localViewAngles.yaw = idMath::AngleNormalize180( localViewAngles.yaw ); + } + + idAngles turretAngles = sphereAngles + localViewAngles; + turret->SetAxis( turretAngles.ToMat3() ); + } + + memcpy( &oldViewAngles, viewAngles, sizeof(idAngles) ); + } +} + +/* +float hhRailShuttle::UpdateJump() { + float jumpDist; + + jumpDist = 0; + if( bJumping ) { + float elapsedTime = MS2SEC( gameLocal.time - jumpStartTime ); + float jumpDuration = spawnArgs.GetFloat( "jumpDuration" ); + if( elapsedTime < jumpDuration ) { + jumpDist = sin( elapsedTime * idMath::PI / jumpDuration ) * spawnArgs.GetFloat( "jumpDistance" ); + } + else { + bJumping = false; + } + } + return jumpDist; +} +*/ + +float hhRailShuttle::UpdateJump( const usercmd_t* cmds ) { + float elapsedTime; + + if( !bJumping) { + return 0; + } + + elapsedTime = MS2SEC( gameLocal.time - jumpStartTime ); + if( jumpStage == 1 ) { + jumpSpeed += jumpAccel * (60.0f * USERCMD_ONE_OVER_HZ); + if( elapsedTime > jumpAccelTime ) { + jumpStage = 2; + } + } + jumpSpeed -= jumpReturnForce * (60.0f * USERCMD_ONE_OVER_HZ); + jumpPosition += jumpSpeed * (60.0f * USERCMD_ONE_OVER_HZ); + if( jumpPosition < 0 ) { + jumpPosition = 0; + if( ++jumpNumBounces > jumpBounceCount ) { + bJumping = false; + StopAirMovementSound(); + StartSound( "snd_jumpland", SND_CHANNEL_ANY ); + } + else { + bBounced = true; + jumpSpeed = -jumpSpeed * jumpBounceFactor; + StartSound( "snd_jumpbounce", SND_CHANNEL_ANY ); + if( (cmds->buttons & BUTTON_ATTACK_ALT) && sphereRadius.IsDone(gameLocal.time) && HasPower(1) && !bDisengaged ) { + Jump(); + } + } + } + SetCockpitParm( VCOCKPIT_PARM_JUMP, jumpPosition ); + return jumpPosition; +} + +void hhRailShuttle::PlayWallMovementSound() { + if (!bWallMovementSoundPlaying) { + StartSound("snd_wallmovestart", SND_CHANNEL_ANY, 0, true); + StartSound("snd_wallmove", SND_CHANNEL_MISC5, 0, true); + bWallMovementSoundPlaying = true; + } +} + +void hhRailShuttle::StopWallMovementSound() { + if (bWallMovementSoundPlaying) { + StopSound(SND_CHANNEL_MISC5, true); + if (!bJumping) { + StartSound("snd_wallmovestop", SND_CHANNEL_ANY, 0, true); + } + bWallMovementSoundPlaying = false; + } +} + +void hhRailShuttle::PlayAirMovementSound() { + if (!bAirMovementSoundPlaying) { + StartSound("snd_thrust", SND_CHANNEL_THRUSTERS, 0, true); + bAirMovementSoundPlaying = true; + } +} + +void hhRailShuttle::StopAirMovementSound() { + if (bAirMovementSoundPlaying) { + StopSound(SND_CHANNEL_THRUSTERS, true); + bAirMovementSoundPlaying = false; + } +} + +void hhRailShuttle::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( !HasPower(1) || bDisengaged ) { + return; + } + if (!InGodMode()) { + if (idStr::Icmp(damageDefName, "damage_railbeam") == 0) { + ConsumePower(currentPower); + PostEventMS(&EV_RestorePower, SEC2MS(spawnArgs.GetFloat("powerOutageDuration"))); + StartSound( "snd_damagestart", SND_CHANNEL_ANY ); + StartSound( "snd_damageloop", SND_CHANNEL_MISC3, 0, true ); + + idEntity *ent = gameLocal.FindEntity( spawnArgs.GetString( "triggerDamage" ) ); + if( ent ) { + ent->PostEventMS( &EV_Activate, 0, inflictor ); + } + + SetCockpitParm( VCOCKPIT_PARM_POWER, -MS2SEC( gameLocal.time ) ); + } + } + //rww - don't want this thing to ever take real damage + //hhVehicle::Damage( inflictor, attacker, dir, damageDefName, 1.0f, location ); +} + +void hhRailShuttle::Event_SetOrbitRadius(float radius, int lerpTime) { + defaultRadius = radius; + float currentRadius = sphereRadius.GetCurrentValue(gameLocal.time); + sphereRadius.Init(gameLocal.time, lerpTime, currentRadius, defaultRadius); + bJumping = false; // Cancel any jumping +} + +void hhRailShuttle::Event_Jump() { + if (!bJumping && sphereRadius.IsDone(gameLocal.time) && HasPower(1) && !bDisengaged) { + Jump(); + } +} + +void hhRailShuttle::Event_FinalJump() { + jumpInitSpeed = spawnArgs.GetFloat("endingInitSpeed"); + jumpAccel = spawnArgs.GetFloat("endingAccel"); + jumpAccelTime = spawnArgs.GetFloat("endingAccelTime"); + jumpReturnForce = spawnArgs.GetFloat("endingReturnForce"); + Jump(); +} + +void hhRailShuttle::Event_IsJumping() { + idThread::ReturnFloat(bJumping); +} + +void hhRailShuttle::Event_Disengage() { + bDisengaged = true; +} + +void hhRailShuttle::Jump() { + bJumping = true; + bBounced = false; + jumpStartTime = gameLocal.time; + jumpStage = 1; + jumpSpeed = jumpInitSpeed; + jumpPosition = 0; + jumpNumBounces = 0; + StopWallMovementSound(); + if( !bDisengaged ) { + StartSound( "snd_jump", SND_CHANNEL_MISC2 ); + } +} + +void hhRailShuttle::Event_FireCannon() { + if( HasPower(1) && !bDisengaged && fireController && fireController->LaunchProjectiles(vec3_zero) ) { + StartSound( "snd_cannon", SND_CHANNEL_ANY ); + fireController->MuzzleFlash(); + fireController->WeaponFeedback(); + SetCockpitParm( VCOCKPIT_PARM_FIRE, -MS2SEC( gameLocal.time ) ); + + idEntity *ent = gameLocal.FindEntity( spawnArgs.GetString( "triggerFire" ) ); + if( ent ) { + ent->PostEventMS( &EV_Activate, 0, this ); + } + + leftArc->Activate( true ); + rightArc->Activate( true ); + + CancelEvents(&EV_HideArcs); + PostEventMS(&EV_HideArcs, spawnArgs.GetFloat("arcDuration")); + } +} + +void hhRailShuttle::Event_HideArcs() { + leftArc->Activate( false ); + rightArc->Activate( false ); +} + +void hhRailShuttle::Event_RestorePower() { + StopSound( SND_CHANNEL_MISC3, true ); + StartSound( "snd_damagestop", SND_CHANNEL_ANY ); + GivePower(100); +} + +void hhRailShuttle::SetCockpitParm( int parmNumber, float value ) { + if( GetPilotInterface() && GetPilotInterface()->IsType( hhPlayerVehicleInterface::Type ) ) { + hhControlHand *hand = static_cast(GetPilotInterface())->GetHandEntity(); + if( hand ) { + hand->SetShaderParm( parmNumber, value ); + } + } +} + +void hhRailShuttle::Event_GetCockpit() { + if( GetPilotInterface() && GetPilotInterface()->IsType( hhPlayerVehicleInterface::Type ) ) { + hhControlHand *hand = static_cast(GetPilotInterface())->GetHandEntity(); + if( hand ) { + idThread::ReturnEntity( hand ); + } else { + idThread::ReturnEntity( NULL ); + } + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/game_railshuttle.h b/neo/prey/Prey/game_railshuttle.h new file mode 100644 index 00000000..2f90411a --- /dev/null +++ b/neo/prey/Prey/game_railshuttle.h @@ -0,0 +1,106 @@ +//========================================================================== +// +// hhShuttle +// +//========================================================================== + +class hhRailShuttle : public hhVehicle { + CLASS_PROTOTYPE( hhRailShuttle ); + +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + virtual void Event_FireCannon() {}; + void Event_HideArcs() {}; + void Event_PostSpawn() {}; + void Event_SetOrbitRadius(float radius, int lerpTime) {}; + void Event_Jump() {}; + void Event_FinalJump() {}; + void Event_IsJumping() {}; + void Event_Disengage() {}; + void Event_RestorePower() {}; + void Event_GetCockpit() {}; + idEntity * GetTurret() { return NULL; } + virtual bool WillAcceptPilot( idActor *act ) { return false; } +#else + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + // Static pilot assessor, for queries when you don't have a vehicle instantiated + static bool ValidPilot( idActor *act ); + virtual bool WillAcceptPilot( idActor *act ); + virtual void AcceptPilot( hhPilotVehicleInterface* pilotInterface ); + virtual void ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ); + virtual void BecomeConsole(); + virtual void BecomeVehicle(); + idEntity * GetTurret() { return turret.GetEntity(); } + + virtual idVec3 GetFireOrigin(); // Called by firecontroller + virtual idMat3 GetFireAxis(); // Called by firecontroller + +protected: + void PlayWallMovementSound(); + void StopWallMovementSound(); + void PlayAirMovementSound(); + void StopAirMovementSound(); + virtual void Event_FireCannon(); + void Event_HideArcs(); + void Event_PostSpawn(); + void Event_SetOrbitRadius(float radius, int lerpTime); + void Event_Jump(); + void Event_FinalJump(); + void Event_IsJumping(); + void Event_Disengage(); + void Jump(); + void Event_RestorePower(); + float UpdateJump( const usercmd_t* cmds ); + void SetCockpitParm( int parmNumber, float value ); + void Event_GetCockpit(); + +protected: + float defaultRadius; + idInterpolate sphereRadius; + idAngles sphereAngles; + idAngles sphereVelocity; + idAngles sphereAcceleration; + idVec3 sphereCenter; + float spherePitchMin; + float spherePitchMax; + float turretYawLimit; + float turretPitchLimit; + float linearFriction; + float terminalVelocity; + idAngles localViewAngles; + idAngles oldViewAngles; + idEntityPtr turret; + idEntityPtr canopy; + idEntityPtr leftArc; + idEntityPtr rightArc; + bool bDisengaged; + bool bJumping; + bool bBounced; + int jumpStartTime; + int jumpStage; + float jumpSpeed; + float jumpPosition; + int jumpNumBounces; + float jumpInitSpeed; + float jumpAccel; + float jumpAccelTime; + float jumpReturnForce; + int jumpBounceCount; + float jumpBounceFactor; + float jumpThrustMovementScale; + float jumpBounceMovementScale; + bool bWallMovementSoundPlaying; + bool bAirMovementSoundPlaying; + //HUMANHEAD PCF mdl 04/29/06 - Added bUpdateViewAngles to prevent view angles from changing on load + bool bUpdateViewAngles; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; diff --git a/neo/prey/Prey/game_renderentity.cpp b/neo/prey/Prey/game_renderentity.cpp new file mode 100644 index 00000000..049cace3 --- /dev/null +++ b/neo/prey/Prey/game_renderentity.cpp @@ -0,0 +1,141 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_ModelDefHandleIsValid( "" ); + +CLASS_DECLARATION( idEntity, hhRenderEntity ) + EVENT( EV_ModelDefHandleIsValid, hhRenderEntity::Event_ModelDefHandleIsValid ) +END_CLASS + +/* +============== +hhRenderEntity::hhRenderEntity +============== +*/ +hhRenderEntity::hhRenderEntity() { + combatModel = NULL; +} + +/* +============== +hhRenderEntity::~hhRenderEntity +============== +*/ +hhRenderEntity::~hhRenderEntity() { + SAFE_DELETE_PTR( combatModel ); +} + +void hhRenderEntity::Save(idSaveGame *savefile) const { + savefile->WriteClipModel( combatModel ); +} + +void hhRenderEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( combatModel ); + + if( combatModel ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( GetModelDefHandle() ); + if( renderEntity ) { + combatModel->Link( gameLocal.clip, this, 0, renderEntity->origin, renderEntity->axis, GetModelDefHandle() ); + } + } +} + +/* +============== +hhRenderEntity::Think +============== +*/ +void hhRenderEntity::Think() { + idEntity::Think(); + + LinkCombatModel( this, GetModelDefHandle() ); +} + +/* +============== +hhRenderEntity::InitCombatModel +============== +*/ +void hhRenderEntity::InitCombatModel( const int renderModelHandle ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( renderModelHandle ); + combatModel->Link( gameLocal.clip );//Force a reclip because our origin and axis hasn't changed + } else { + combatModel = new idClipModel( renderModelHandle ); + HH_ASSERT( combatModel ); + } +} + +/* +============== +hhRenderEntity::LinkCombatModel +============== +*/ +void hhRenderEntity::LinkCombatModel( idEntity* self, const int renderModelHandle ) { + if( combatModel && self && renderModelHandle != -1 ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if( !renderEntity ) { + return; + } + + if( combatModel->GetOrigin() != renderEntity->origin || combatModel->GetAxis() != renderEntity->axis || combatModel->GetBounds() != renderEntity->bounds ) { + combatModel->Link( gameLocal.clip, self, 0, renderEntity->origin, renderEntity->axis, renderModelHandle ); + } + } +} + +/* +============== +hhRenderEntity::Present + +AOBMERGE: PLEASE REMOVE THIS WHEN WE GET idRenderEntity +============== +*/ +void hhRenderEntity::Present( void ) { + PROFILE_SCOPE("Present", PROFMASK_NORMAL); + + if ( !gameLocal.isNewFrame ) { + 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 + if ( cameraTarget && gameLocal.InPlayerPVS( this ) ) { + 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 ); + //HUMANHEAD: aob - needed for combat models + PostEventMS( &EV_ModelDefHandleIsValid, 0 ); + //HUMANHEAD END + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +============== +hhRenderEntity::Event_ModelDefHandleIsValid +============== +*/ +void hhRenderEntity::Event_ModelDefHandleIsValid() { + if( spawnArgs.GetBool("useCombatModel") ) { + InitCombatModel( GetModelDefHandle() ); + LinkCombatModel( this, GetModelDefHandle() ); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/game_renderentity.h b/neo/prey/Prey/game_renderentity.h new file mode 100644 index 00000000..58324ec2 --- /dev/null +++ b/neo/prey/Prey/game_renderentity.h @@ -0,0 +1,31 @@ +#ifndef __GAME_RENDERENTITY_H +#define __GAME_RENDERENTITY_H + +extern const idEventDef EV_ModelDefHandleIsValid; + +class hhRenderEntity : public idEntity { + CLASS_PROTOTYPE( hhRenderEntity ); + + protected: + hhRenderEntity(); + virtual ~hhRenderEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + + virtual void InitCombatModel( const int renderModelHandle ); + virtual void LinkCombatModel( idEntity* self, const int renderModelHandle ); + + public: + virtual void Present(); + + protected: + void Event_ModelDefHandleIsValid(); + + protected: + idClipModel* combatModel; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_safeDeathVolume.cpp b/neo/prey/Prey/game_safeDeathVolume.cpp new file mode 100644 index 00000000..20d4e0e2 --- /dev/null +++ b/neo/prey/Prey/game_safeDeathVolume.cpp @@ -0,0 +1,136 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhSafeResurrectionVolume +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION( idEntity, hhSafeResurrectionVolume ) + EVENT( EV_Enable, hhSafeResurrectionVolume::Event_Enable ) + EVENT( EV_Disable, hhSafeResurrectionVolume::Event_Disable ) +END_CLASS + +/* +=============== +hhSafeResurrectionVolume::Spawn +=============== +*/ +void hhSafeResurrectionVolume::Spawn() { + PostEventMS( (spawnArgs.GetBool("enabled", "1")) ? &EV_Enable : &EV_Disable, 0); +} + +/* +=============== +hhSafeResurrectionVolume::PickRandomPoint +=============== +*/ +void hhSafeResurrectionVolume::PickRandomPoint( idVec3& origin, idMat3& axis ) { + idEntity* entity = PickRandomTarget(); + + if ( !entity ) { // Error message that means something to the designers - cjr + gameLocal.Error( "hhSafeResurrectionZone::PickRandomPoint: No targets found within resurrection zone.\n" ); + } + + origin = entity->GetOrigin(); + axis = entity->GetAxis(); +} + +/* +=============== +hhSafeResurrectionVolume::DetermineContents +=============== +*/ +int hhSafeResurrectionVolume::DetermineContents() const { + return CONTENTS_DEATHVOLUME; +} + +/* +=============== +hhSafeResurrectionVolume::Event_Enable +=============== +*/ +void hhSafeResurrectionVolume::Event_Enable() { + GetPhysics()->EnableClip(); + GetPhysics()->SetContents( DetermineContents() ); +} + +/* +=============== +hhSafeResurrectionVolume::Event_Disable +=============== +*/ +void hhSafeResurrectionVolume::Event_Disable() { + GetPhysics()->DisableClip(); + GetPhysics()->SetContents( 0 ); +} + +//----------------------------------------------------------------------- +// +// hhSafeDeathVolume +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION( hhSafeResurrectionVolume, hhSafeDeathVolume ) + EVENT( EV_Touch, hhSafeDeathVolume::Event_Touch ) +END_CLASS + +/* +=============== +hhSafeDeathVolume::Spawn +=============== +*/ +void hhSafeDeathVolume::Spawn() { + GetPhysics()->SetContents( DetermineContents() ); +} + +/* +=============== +hhSafeDeathVolume::DetermineContents +=============== +*/ +int hhSafeDeathVolume::DetermineContents() const { + return CONTENTS_DEATHVOLUME | CONTENTS_TRIGGER; +} + +/* +=============== +hhSafeDeathVolume::IsValid +=============== +*/ +bool hhSafeDeathVolume::IsValid( const hhPlayer* player ) { + if( !player || player->IsDeathWalking() || player->InVehicle() ) { + return false; + } + + return true; +} + +/* +=============== +hhSafeDeathVolume::Event_Touch +=============== +*/ +void hhSafeDeathVolume::Event_Touch( idEntity *other, trace_t *trace ) { + if( !other ) { + return; + } + + if( !other->IsType(hhPlayer::Type) ) { + return; + } + + hhPlayer* player = static_cast( other ); + if( !IsValid(player) ) { + return; + } + + if( player->IsSpiritOrDeathwalking() ) { + player->StopSpiritWalk(); + } else { + player->Kill( false, false ); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/game_safeDeathVolume.h b/neo/prey/Prey/game_safeDeathVolume.h new file mode 100644 index 00000000..6d205ed2 --- /dev/null +++ b/neo/prey/Prey/game_safeDeathVolume.h @@ -0,0 +1,43 @@ +#ifndef __HH_SAFE_DEATH_VOLUME_H +#define __HH_SAFE_DEATH_VOLUME_H + +//----------------------------------------------------------------------- +// +// hhSafeResurrectionVolume +// +//----------------------------------------------------------------------- +class hhSafeResurrectionVolume : public idEntity { + CLASS_PROTOTYPE( hhSafeResurrectionVolume ); + + public: + void Spawn(); + + void PickRandomPoint( idVec3& origin, idMat3& axis ); + + protected: + virtual int DetermineContents() const; + + void Event_Enable(); + void Event_Disable(); +}; + +//----------------------------------------------------------------------- +// +// hhSafeResurrectionVolume +// +//----------------------------------------------------------------------- +class hhSafeDeathVolume : public hhSafeResurrectionVolume { + CLASS_PROTOTYPE( hhSafeDeathVolume ); + + public: + void Spawn(); + + protected: + virtual bool IsValid( const hhPlayer* player ); + virtual int DetermineContents() const; + + protected: + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_securityeye.cpp b/neo/prey/Prey/game_securityeye.cpp new file mode 100644 index 00000000..fd6c468d --- /dev/null +++ b/neo/prey/Prey/game_securityeye.cpp @@ -0,0 +1,801 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +/********************************************************************** + +hhSecurityEyeBase + +**********************************************************************/ + +CLASS_DECLARATION( idEntity, hhSecurityEyeBase ) + EVENT( EV_Enable, hhSecurityEyeBase::Event_Enable ) + EVENT( EV_Disable, hhSecurityEyeBase::Event_Disable ) + EVENT( EV_Activate, hhSecurityEyeBase::Event_Activate ) +END_CLASS + +void hhSecurityEyeBase::Spawn() { + SpawnEye(); +} + +void hhSecurityEyeBase::Save(idSaveGame *savefile) const { + m_pEye.Save(savefile); +} + +void hhSecurityEyeBase::Restore( idRestoreGame *savefile ) { + m_pEye.Restore(savefile); +} + +void hhSecurityEyeBase::TransferArg(idDict &Args, const char *key) { + const idKeyValue *kv = NULL; + do { + kv = spawnArgs.MatchPrefix(key, kv); + if (kv) { + Args.Set(kv->GetKey().c_str(), kv->GetValue().c_str()); + } + } while (kv != NULL); +} + +void hhSecurityEyeBase::SpawnEye() { + + idVec3 origin = GetOrigin(); + idMat3 axis = GetAxis(); + idVec3 offset = spawnArgs.GetVector("offset_eye"); + + idDict Args; + Args.SetVector( "origin", origin + offset*axis ); + Args.SetMatrix( "rotation", axis ); + + TransferArg(Args, "minPitch"); + TransferArg(Args, "maxPitch"); + TransferArg(Args, "minYaw"); + TransferArg(Args, "maxYaw"); + TransferArg(Args, "startPitch"); + TransferArg(Args, "startYaw"); + TransferArg(Args, "pitchRate"); + TransferArg(Args, "yawRate"); + TransferArg(Args, "fov"); + TransferArg(Args, "pathScanRate"); + TransferArg(Args, "usePathScan"); + TransferArg(Args, "triggerOnce"); + TransferArg(Args, "lengthBeam"); + TransferArg(Args, "enabled"); + TransferArg(Args, "health"); + TransferArg(Args, "pathScanNode"); + TransferArg(Args, "call"); + TransferArg(Args, "callRef"); + TransferArg(Args, "callRefActivator"); + TransferArg(Args, "triggerBehavior"); + TransferArg(Args, "target"); + + m_pEye = gameLocal.SpawnObject( spawnArgs.GetString("def_eye"), &Args ); + HH_ASSERT( m_pEye.IsValid() && m_pEye.GetEntity() ); + + m_pEye->fl.noRemoveWhenUnbound = true; + m_pEye->Bind(this, true); + + if (m_pEye->IsType(hhSecurityEye::Type)) { + static_cast(m_pEye.GetEntity())->SetBase(this); + } + if (spawnArgs.GetBool("nobase")) { + Hide(); + } +} + +void hhSecurityEyeBase::Event_Activate(idEntity *pActivator) { + if (m_pEye.IsValid()) { + m_pEye->ProcessEvent(&EV_Activate, pActivator); // Pass activate messages to eye + } +} +void hhSecurityEyeBase::Event_Enable() { + if (m_pEye.IsValid()) { + m_pEye->ProcessEvent(&EV_Enable); // Pass enable messages to eye + } +} +void hhSecurityEyeBase::Event_Disable() { + if (m_pEye.IsValid()) { + m_pEye->ProcessEvent(&EV_Disable); // Pass disable messages to eye + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +/********************************************************************** + +hhSecurityEye + +**********************************************************************/ +const idEventDef EV_Notify("notify", "e"); + +CLASS_DECLARATION( idEntity, hhSecurityEye ) + EVENT( EV_Enable, hhSecurityEye::Event_Enable ) + EVENT( EV_Disable, hhSecurityEye::Event_Disable ) + EVENT( EV_Notify, hhSecurityEye::Event_Notify ) + EVENT( EV_Activate, hhSecurityEye::Event_Activate ) +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +/* +================ +hhSecurityEye::Spawn +================ +*/ +void hhSecurityEye::Spawn() { + m_pBase = NULL; + m_pTrigger = NULL; + m_bTriggerOnce = spawnArgs.GetBool("triggerOnce"); + m_fCachedScanFovCos = idMath::Cos( DEG2RAD(spawnArgs.GetFloat("fov")) ); + m_lengthBeam = spawnArgs.GetFloat("lengthBeam", "4096"); + m_offsetTrigger = spawnArgs.GetVector("offset_trigger"); + + SetupRotationParms(); + SpawnTrigger(); + m_bUsePathScan = InitPathList(); + + GetPhysics()->SetContents(CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW); + fl.takedamage = false; // CJR: Set to be non-destructible at 3DR's request + + m_iPVSArea = gameLocal.pvs.GetPVSArea( GetOrigin() ); + + if( spawnArgs.GetBool("enabled", "1") ) { + StartScanning(); + } else { + EnterIdleState(); + } + + BecomeActive( TH_TICKER ); +} + +void hhSecurityEye::Save(idSaveGame *savefile) const { + savefile->WriteInt( state ); + savefile->WriteObject(m_pTrigger); + savefile->WriteBool(m_bTriggerOnce); + savefile->WriteFloat(m_fCachedScanFovCos); + savefile->WriteInt(m_iPVSArea); + savefile->WriteAngles(m_StartAngles); + savefile->WriteAngles(m_MaxLookAngles); + savefile->WriteAngles(m_MinLookAngles); + savefile->WriteBool(m_bPitchDirection); + savefile->WriteBool(m_bYawDirection); + savefile->WriteStringList(m_PathScanNodes); + savefile->WriteInt(m_iPathScanNodeIndex); + savefile->WriteBool(m_bUsePathScan); + savefile->WriteFloat(m_fPathScanRate); + savefile->WriteObject(m_pBase); + m_Target.Save(savefile); + + savefile->WriteFloat( currentYaw.GetStartTime() ); // idInterpolate + savefile->WriteFloat( currentYaw.GetDuration() ); + savefile->WriteFloat( currentYaw.GetStartValue() ); + savefile->WriteFloat( currentYaw.GetEndValue() ); + + savefile->WriteFloat( currentPitch.GetStartTime() ); // idInterpolate + savefile->WriteFloat( currentPitch.GetDuration() ); + savefile->WriteFloat( currentPitch.GetStartValue() ); + savefile->WriteFloat( currentPitch.GetEndValue() ); + + savefile->WriteAngles(m_RotationRate); + savefile->WriteFloat(m_lengthBeam); + savefile->WriteVec3(m_offsetTrigger); + + savefile->WriteAngles(m_LookAngles); +} + +void hhSecurityEye::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadInt( reinterpret_cast ( state ) ); + savefile->ReadObject(reinterpret_cast (m_pTrigger)); + savefile->ReadBool(m_bTriggerOnce); + savefile->ReadFloat(m_fCachedScanFovCos); + savefile->ReadInt(m_iPVSArea); + savefile->ReadAngles(m_StartAngles); + savefile->ReadAngles(m_MaxLookAngles); + savefile->ReadAngles(m_MinLookAngles); + savefile->ReadBool(m_bPitchDirection); + savefile->ReadBool(m_bYawDirection); + savefile->ReadStringList(m_PathScanNodes); + savefile->ReadInt(m_iPathScanNodeIndex); + savefile->ReadBool(m_bUsePathScan); + savefile->ReadFloat(m_fPathScanRate); + savefile->ReadObject(reinterpret_cast (m_pBase)); + m_Target.Restore(savefile); + + savefile->ReadFloat( set ); // idInterpolate + currentYaw.SetStartTime( set ); + savefile->ReadFloat( set ); + currentYaw.SetDuration( set ); + savefile->ReadFloat( set ); + currentYaw.SetStartValue( set ); + savefile->ReadFloat( set ); + currentYaw.SetEndValue( set ); + + savefile->ReadFloat( set ); // idInterpolate + currentPitch.SetStartTime( set ); + savefile->ReadFloat( set ); + currentPitch.SetDuration( set ); + savefile->ReadFloat( set ); + currentPitch.SetStartValue( set ); + savefile->ReadFloat( set ); + currentPitch.SetEndValue( set ); + + savefile->ReadAngles(m_RotationRate); + savefile->ReadFloat(m_lengthBeam); + savefile->ReadVec3(m_offsetTrigger); + + savefile->ReadAngles(m_LookAngles); +} + +/* +================ +hhSecurityEye::~hhSecurityEye +================ +*/ +hhSecurityEye::~hhSecurityEye() { + SAFE_REMOVE( m_pTrigger ); + + //Used in case you use the remove command from the console + StopSound( SND_CHANNEL_ANY ); +} + +/* +===================== +hhSecurityEye::DormantBegin +===================== +*/ +void hhSecurityEye::DormantBegin() { + idEntity::DormantBegin(); + + if( state == StatePathScanning || state == StateAreaScanning ) { + DisableTrigger(); + } +} + +/* +===================== +hhSecurityEye::DormantEnd +===================== +*/ +void hhSecurityEye::DormantEnd() { + idEntity::DormantEnd(); + + if( state == StatePathScanning || state == StateAreaScanning ) { + EnableTrigger(); + } +} + +/* +===================== +hhSecurityEye::SetupRotationParms +===================== +*/ +void hhSecurityEye::SetupRotationParms() { + m_bPitchDirection = 1; + m_bYawDirection = 1; + m_StartAngles.Set( spawnArgs.GetFloat("startPitch"), spawnArgs.GetFloat("startYaw"), 0.0f ); + m_LookAngles = m_StartAngles; + + m_MaxLookAngles.Set( spawnArgs.GetFloat("maxPitch"), spawnArgs.GetFloat("maxYaw"), 0.0f ); + m_MinLookAngles.Set( spawnArgs.GetFloat("minPitch"), spawnArgs.GetFloat("minYaw"), 0.0f ); + m_RotationRate.Set ( spawnArgs.GetFloat("pitchRate"), spawnArgs.GetFloat("yawRate"), 0.0f ); + + currentPitch.Init(gameLocal.time, 0, m_StartAngles.pitch, m_StartAngles.pitch); + currentYaw.Init(gameLocal.time, 0, m_StartAngles.yaw, m_StartAngles.yaw); +} + +// Turn to angles using standard rates, angles should be in -180..180 local space +void hhSecurityEye::TurnTo(idAngles &ang) { + float curYaw, curPitch, delta; + int duration; + + if (m_RotationRate.yaw) { + curYaw = currentYaw.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - ang.yaw); + duration = SEC2MS(delta / m_RotationRate.yaw); + currentYaw.Init(gameLocal.time, duration, curYaw, ang.yaw); + } + + if (m_RotationRate.pitch) { + curPitch = currentPitch.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - ang.yaw); + duration = SEC2MS(delta / m_RotationRate.pitch); + currentPitch.Init(gameLocal.time, duration, curPitch, ang.pitch); + } +} + +void hhSecurityEye::LookAtLinear(idVec3 &pos) { + float curYaw, curPitch, delta; + int durationYaw, durationPitch, duration; + idVec3 localDir; + idVec3 dir = pos - GetOrigin(); + dir.Normalize(); + GetBaseAxis().ProjectVector(dir, localDir); + + idAngles localAngles; + localAngles.roll = 0.0f; + localAngles.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + localAngles.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + + curYaw = currentYaw.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - localAngles.yaw); + durationYaw = SEC2MS(delta / m_fPathScanRate); + + curPitch = currentPitch.GetCurrentValue(gameLocal.time); + delta = idMath::Fabs(curYaw - localAngles.yaw); + durationPitch = SEC2MS(delta / m_fPathScanRate); + + // Both converge together at the same time + duration = max(durationYaw, durationPitch); + currentYaw.Init(gameLocal.time, duration, curYaw, localAngles.yaw); + currentPitch.Init(gameLocal.time, duration, curPitch, localAngles.pitch); +} + +// Returns base rotation, all the local angles are relative to this +idMat3 hhSecurityEye::GetBaseAxis() { + assert(m_pBase); + return m_pBase->GetAxis(); +} + +// Set rotation of eye based on local angles +void hhSecurityEye::UpdateRotation() { + idAngles localAngles; + localAngles.Set(currentPitch.GetCurrentValue(gameLocal.time), currentYaw.GetCurrentValue(gameLocal.time), 0.0f); + SetAxis( localAngles.ToMat3() ); +} + +void hhSecurityEye::SetBase(idEntity *ent) { + m_pBase = ent; +} + + +/* +===================== +hhSecurityEye::GetRenderView +===================== +*/ +renderView_t* hhSecurityEye::GetRenderView() { + + renderView = idEntity::GetRenderView(); + + idVec3 ViewOffset = spawnArgs.GetVector("offset_view"); + idMat3 ViewAxis = GetAxis(); + idVec3 ViewOrigin = GetOrigin() + ViewOffset*ViewAxis; + + gameLocal.CalcFov( spawnArgs.GetFloat("fov", "90"), renderView->fov_x, renderView->fov_y ); //HUMANHEAD premerge bjk + renderView->viewaxis = ViewAxis; + renderView->vieworg = ViewOrigin; + + return renderView; +} + +/* +================ +hhSecurityEye::GetTriggerArgs +================ +*/ +idDict* hhSecurityEye::GetTriggerArgs( idDict* pArgs ) { + assert( pArgs ); + + const idKeyValue* pKeyValue = NULL; + + pArgs->Set( "call", spawnArgs.GetString("call") ); + pArgs->Set( "callRef", spawnArgs.GetString("callRef") ); + pArgs->Set( "callRefActivator", spawnArgs.GetString("callRefActivator") ); + + pArgs->Set( "triggerBehavior", spawnArgs.GetString("triggerBehavior") ); + for( int iIndex = spawnArgs.GetNumKeyVals() - 1; iIndex >= 0 ; --iIndex ) { + pKeyValue = spawnArgs.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "trigger_class", 13 ) ) { + pArgs->Set( pKeyValue->GetKey(), pKeyValue->GetValue() ); + } + else if ( !pKeyValue->GetKey().Cmpn( "target", 6 ) ) { + pArgs->Set( pKeyValue->GetKey(), pKeyValue->GetValue() ); + } + } + + //Tripwire args + pArgs->SetFloat( "lengthBeam", m_lengthBeam ); + pArgs->SetBool( "rigidBeamLength", 1 ); + pArgs->Set( "beam", spawnArgs.GetString("beam") ); + //Tripwire args end + + return pArgs; +} + +/* +================ +hhSecurityEye::SpawnTrigger +================ +*/ +void hhSecurityEye::SpawnTrigger() { + + idVec3 origin = GetPhysics()->GetOrigin(); + idMat3 axis = GetPhysics()->GetAxis(); + + idDict Args; + Args.SetVector( "origin", origin + m_offsetTrigger*axis ); + Args.SetMatrix( "rotation", axis ); + m_pTrigger = static_cast( gameLocal.SpawnObject(spawnArgs.GetString("def_trigger"), GetTriggerArgs( &Args )) ); + HH_ASSERT( m_pTrigger ); + + m_pTrigger->SetOwner( this ); + m_pTrigger->Bind( this, true ); +} + +/* +============ +hhSecurityEye::InitPathList +============ +*/ +bool hhSecurityEye::InitPathList() { + const idKeyValue* pKeyValue = NULL; + + m_PathScanNodes.Clear(); + for( int iIndex = spawnArgs.GetNumKeyVals() - 1; iIndex >= 0; --iIndex ) { + pKeyValue = spawnArgs.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "pathScanNode", 12 ) && pKeyValue->GetValue().Length() ) { + m_PathScanNodes.Append( pKeyValue->GetValue() ); + } + } + + m_iPathScanNodeIndex = 0; + m_fPathScanRate = spawnArgs.GetFloat("pathScanRate"); + + return ( m_PathScanNodes.Num() && spawnArgs.GetBool( "usePathScan" ) ); +} + +/* +================ +hhSecurityEye::Ticker +================ +*/ +void hhSecurityEye::Ticker() { + switch( state ) { + case StateIdle: + if (currentYaw.IsDone(gameLocal.time) && currentPitch.IsDone(gameLocal.time)) { + BecomeInactive( TH_TICKER ); + } + break; + case StateTracking: + break; + + case StateAreaScanning: + if (currentPitch.IsDone(gameLocal.time)) { + m_bPitchDirection ^= 1; + if( !m_bPitchDirection ) { + m_LookAngles.pitch = m_MinLookAngles.pitch; + } else { + m_LookAngles.pitch = m_MaxLookAngles.pitch; + } + TurnTo( m_LookAngles ); + } + + if (currentYaw.IsDone(gameLocal.time)) { + m_bYawDirection ^= 1; + if( !m_bYawDirection ) { + m_LookAngles.yaw = m_MinLookAngles.yaw; + } else { + m_LookAngles.yaw = m_MaxLookAngles.yaw; + } + TurnTo( m_LookAngles ); + } + break; + + case StatePathScanning: + idEntity* pEntity = NULL; + + if (currentYaw.IsDone(gameLocal.time) && currentPitch.IsDone(gameLocal.time)) { + pEntity = VerifyPathScanNode( m_iPathScanNodeIndex ); + if( pEntity ) { + idVec3 origin = pEntity->GetPhysics()->GetOrigin(); + LookAtLinear( origin ); + + m_iPathScanNodeIndex = (m_iPathScanNodeIndex + 1) % m_PathScanNodes.Num(); + } + } + break; + } + + UpdateRotation(); +} + +/* +============ +hhSecurityEye::DetermineEntityPosition +============ +*/ +idVec3 hhSecurityEye::DetermineEntityPosition( idEntity* pEntity ) { + if( pEntity && pEntity->IsType( idActor::Type ) ) { + return static_cast(pEntity)->GetEyePosition(); + } + + return pEntity->GetPhysics()->GetOrigin(); +} + +/* +================ +hhSecurityEye::CanSeeTarget +================ +*/ +bool hhSecurityEye::CanSeeTarget( idEntity* pEntity ) { + float fDist; + trace_t TraceInfo; + idVec3 Dir; + pvsHandle_t PVSHandle; + idVec3 EyePos; + idMat3 EyeAxis; + idVec3 TraceEnd; + + PVSHandle = gameLocal.pvs.SetupCurrentPVS( m_iPVSArea ); + + if ( !pEntity || ( pEntity->fl.notarget ) ) { + goto CANT_SEE; + } + + // if there is no way we can see this player + if( !gameLocal.pvs.InCurrentPVS( PVSHandle, pEntity->GetPVSAreas(), pEntity->GetNumPVSAreas() ) ) { + goto CANT_SEE; + } + + TraceEnd = (pEntity->IsType(idActor::Type)) ? static_cast(pEntity)->GetEyePosition() : pEntity->GetPhysics()->GetOrigin(); + Dir = TraceEnd - GetPhysics()->GetOrigin(); + fDist = Dir.Normalize(); + + if( fDist > m_lengthBeam ) { + goto CANT_SEE; + } + + EyeAxis = GetAxis(); + EyePos = GetOrigin() + m_offsetTrigger * EyeAxis; + + if( Dir * EyeAxis[0] < m_fCachedScanFovCos ) { + goto CANT_SEE; + } + + gameLocal.clip.TracePoint( TraceInfo, EyePos, TraceEnd, MASK_VISIBILITY, this ); + if( TraceInfo.fraction == 1.0f || TraceInfo.c.entityNum == pEntity->entityNumber ) { + gameLocal.pvs.FreeCurrentPVS( PVSHandle ); + return true; + } + +CANT_SEE: + gameLocal.pvs.FreeCurrentPVS( PVSHandle ); + + return false; +} + +/* +===================== +hhSecurityEye::EnableTrigger +===================== +*/ +void hhSecurityEye::EnableTrigger() { + if( m_pTrigger ) { + m_pTrigger->ProcessEvent( &EV_Enable ); + } +} + +/* +===================== +hhSecurityEye::DisableTrigger +===================== +*/ +void hhSecurityEye::DisableTrigger() { + if( m_pTrigger ) { + m_pTrigger->ProcessEvent( &EV_Disable ); + } +} + +/* +============ +hhSecurityEye::Killed +============ +*/ +void hhSecurityEye::Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ) { + if( state != StateDead ) { + //Only trigger targets when active + if( state != StateIdle ) { + ActivateTargets( pAttacker ); + } + + EnterDeadState(); + } +} + +/* +============ +hhSecurityEye::StartScanning +============ +*/ +void hhSecurityEye::StartScanning() { + if( !m_bUsePathScan ) { + EnterAreaScanningState(); + } else { + EnterPathScanningState(); + } +} + +/* +============ +hhSecurityEye::VerifyLookTarget +============ +*/ +idEntity* hhSecurityEye::VerifyPathScanNode( int& iIndex ) { + return gameLocal.FindEntity( m_PathScanNodes[iIndex].c_str() ); +} + +/* +============ +hhSecurityEye::Event_Enable +============ +*/ +void hhSecurityEye::Event_Enable() { + switch( state ) { + case StateIdle: + StartScanning(); + break; + } +} + +/* +============ +hhSecurityEye::Event_Disable +============ +*/ +void hhSecurityEye::Event_Disable() { + switch( state ) { + case StateTracking: + case StatePathScanning: + case StateAreaScanning: + EnterIdleState(); + break; + } +} + +/* +============ +hhSecurityEye::Event_Notify +============ +*/ +void hhSecurityEye::Event_Notify( idEntity* pActivator ) { + //Not sure of the behavior wanted if m_bTriggerOnce is true + if( pActivator && pActivator->IsType(idActor::Type) ) { + const idActor* pActor = static_cast( pActivator ); + } + + switch( state ) { + case StateAreaScanning: + case StatePathScanning: + HH_ASSERT( pActivator ); + m_Target = pActivator; + EnterTrackingState(); + break; + case StateTracking: + break; + } +} + +/* +============ +hhSecurityEye::Event_Activate +============ +*/ +void hhSecurityEye::Event_Activate( idEntity* pActivator ) { + switch( state ) { + case StateIdle: + StartScanning(); + break; + case StatePathScanning: + case StateAreaScanning: + case StateTracking: + m_Target = NULL; + EnterIdleState(); + break; + } +} + +/* +================ +hhSecurityEye::EnterDeadState +================ +*/ +void hhSecurityEye::EnterDeadState() { + state = StateDead; + + hhFxInfo fxInfo; + + DisableTrigger(); + + StopSound( SND_CHANNEL_ANY ); + + fxInfo.SetNormal( GetAxis()[0] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_detonate", m_pBase->GetOrigin(), GetAxis(), &fxInfo ); + + idVec3 Position = spawnArgs.GetVector("offset_debris"); + Position = GetPhysics()->GetOrigin() + Position * GetPhysics()->GetAxis(); + idVec3 Forward = GetAxis()[0]; + idVec3 Velocity = Forward*10; + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), Position, &Forward, &Velocity, 1); + + // Cause the base to flicker when the ball is destroyed + if( m_pBase ) { + m_pBase->SetShaderParm( SHADERPARM_MODE, 1 ); // Activate the flicker effect + m_pBase->SetShaderParm( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.time )); + } + + Hide(); + GetPhysics()->DisableClip(); + int length = 0; + StartSound("snd_explode", SND_CHANNEL_ANY, 0, false, &length); + PostEventMS( &EV_Remove, length ); + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhSecurityEye::EnterIdleState +================ +*/ +void hhSecurityEye::EnterIdleState() { + state = StateIdle; + + DisableTrigger(); + + TurnTo( m_StartAngles ); + + StopSound( SND_CHANNEL_ANY ); +} + +/* +================ +hhSecurityEye::EnterAreaScanningState +================ +*/ +void hhSecurityEye::EnterAreaScanningState() { + state = StateAreaScanning; + + m_LookAngles = m_StartAngles; + TurnTo( m_StartAngles ); + + EnableTrigger(); + + StartSound("snd_move", SND_CHANNEL_ANY); + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhSecurityEye::EnterPathScanningState +================ +*/ +void hhSecurityEye::EnterPathScanningState() { + state = StatePathScanning; + + HH_ASSERT( m_PathScanNodes.Num() ); + + EnableTrigger(); + + StartSound("snd_move", SND_CHANNEL_ANY); + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhSecurityEye::EnterTrackingState +================ +*/ +void hhSecurityEye::EnterTrackingState() { + state = StateTracking; + + DisableTrigger(); + StopSound( SND_CHANNEL_ANY ); + + BecomeActive( TH_TICKER ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/game_securityeye.h b/neo/prey/Prey/game_securityeye.h new file mode 100644 index 00000000..21396b39 --- /dev/null +++ b/neo/prey/Prey/game_securityeye.h @@ -0,0 +1,134 @@ +#ifndef __HH_SECURITYEYE_H +#define __HH_SECURITYEYE_H + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +extern const idEventDef EV_Notify; + +/********************************************************************** + +hhSecurityEyeBase + +**********************************************************************/ +class hhSecurityEyeBase : public idEntity { + CLASS_PROTOTYPE( hhSecurityEyeBase ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SpawnEye(); + void TransferArg(idDict &Args, const char *key); + +protected: + void Event_Activate( idEntity* pActivator ); + void Event_Enable(); + void Event_Disable(); + +protected: + idEntityPtr m_pEye; +}; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + +/********************************************************************** + +hhSecurityEye + +**********************************************************************/ +class hhSecurityEye : public idEntity { + CLASS_PROTOTYPE( hhSecurityEye ); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_Enable() {}; + void Event_Disable() {}; + void Event_Notify( idEntity* pActivator ) {}; + void Event_Activate( idEntity* pActivator ) {}; +#else + public: + hhSecurityEye() : m_pTrigger( NULL ) { } + virtual ~hhSecurityEye(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void SetBase(idEntity *ent); + + virtual renderView_t* GetRenderView(); + + protected: + void Ticker(); + idDict* GetTriggerArgs( idDict* pArgs ); + void SpawnTrigger(); + void SpawnBase(); + bool InitPathList(); + + virtual void DormantBegin(); + virtual void DormantEnd(); + + void EnableTrigger(); + void DisableTrigger(); + + idVec3 DetermineEntityPosition( idEntity* pEntity ); + bool CanSeeTarget( idEntity* pEntity ); + virtual void Killed( idEntity *pInflictor, idEntity *pAttacker, int iDamage, const idVec3 &Dir, int iLocation ); + + void StartScanning(); + idEntity* VerifyPathScanNode( int& iIndex ); + + protected: + void Event_Enable(); + void Event_Disable(); + void Event_Notify( idEntity* pActivator ); + void Event_Activate( idEntity* pActivator ); + + protected: + enum States { + StateIdle = 0, + StateAreaScanning, + StatePathScanning, + StateTracking, // Tracking state is now just a stopover state that means it was triggered. + // It is needed so that the next time it's triggered, it will become inactive + StateDead + } state; + + void EnterDeadState(); + void EnterIdleState(); + void EnterAreaScanningState(); + void EnterPathScanningState(); + void EnterTrackingState(); + + void UpdateRotation(); + void LookAtLinear(idVec3 &pos); + void TurnTo(idAngles &ang); + void SetupRotationParms(); + idMat3 GetBaseAxis(); + + protected: + hhTriggerTripwire* m_pTrigger; + + bool m_bTriggerOnce; + float m_fCachedScanFovCos; + int m_iPVSArea; + + idAngles m_StartAngles; + idAngles m_MaxLookAngles; + idAngles m_MinLookAngles; + idAngles m_LookAngles; + bool m_bPitchDirection; + bool m_bYawDirection; + + idList m_PathScanNodes; + int m_iPathScanNodeIndex; + bool m_bUsePathScan; + float m_fPathScanRate; + + idEntity *m_pBase; + idEntityPtr m_Target; + + idInterpolate currentYaw; // These are local angles, relative to base rotation + idInterpolate currentPitch; // These are local angles, relative to base rotation + idAngles m_RotationRate; + float m_lengthBeam; + idVec3 m_offsetTrigger; +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_shuttle.cpp b/neo/prey/Prey/game_shuttle.cpp new file mode 100644 index 00000000..62a51d54 --- /dev/null +++ b/neo/prey/Prey/game_shuttle.cpp @@ -0,0 +1,1057 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* + method: + walk up to ship console, activate gui + gui sends command to vehicle to take control + vehicle animates out of console around you + control hand is added + + model specs: + min: -64 -50 -64 + max: 64 50 64 + + needs to be centered about the origin (in all axes) + needs to fit a pilot collision box inside in his "piloting" animation + needs to be collapsable into the bottom console +*/ + + +//========================================================================== +// +// hhTractorBeam +// +//========================================================================== + +CLASS_DECLARATION(idClass, hhTractorBeam) +END_CLASS + +hhTractorBeam::hhTractorBeam() { + bAllow = true; + owner = NULL; + bindController = NULL; + traceController = NULL; + traceTarget = NULL; + beam = NULL; + targetLocalOffset = vec3_origin; + targetID = 0; + bActive = false; + beamClientPredictTime = 0; +} + +void hhTractorBeam::Save(idSaveGame *savefile) const { + owner.Save(savefile); + beam.Save(savefile); + traceTarget.Save(savefile); + bindController.Save(savefile); + traceController.Save(savefile); + + savefile->WriteStaticObject(feedbackForce); + savefile->WriteInt(tractorCost); + savefile->WriteBool(bAllow); + savefile->WriteBool(bActive); + savefile->WriteVec3(offsetTraceStart); + savefile->WriteVec3(offsetTraceEnd); + savefile->WriteVec3(offsetEquilibrium); + savefile->WriteVec3(targetLocalOffset); + savefile->WriteInt(targetID); +} + +void hhTractorBeam::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); + beam.Restore(savefile); + traceTarget.Restore(savefile); + bindController.Restore(savefile); + traceController.Restore(savefile); + + savefile->ReadStaticObject(feedbackForce); + savefile->ReadInt(tractorCost); + savefile->ReadBool(bAllow); + savefile->ReadBool(bActive); + savefile->ReadVec3(offsetTraceStart); + savefile->ReadVec3(offsetTraceEnd); + savefile->ReadVec3(offsetEquilibrium); + savefile->ReadVec3(targetLocalOffset); + savefile->ReadInt(targetID); +} + +void hhTractorBeam::WriteToSnapshot( idBitMsgDelta &msg ) const { + //don't need to sync the beam and other components that are purely client-side + + msg.WriteBits(owner.GetSpawnId(), 32); + msg.WriteBits(traceTarget.GetSpawnId(), 32); + msg.WriteBits(targetID, 32); + + msg.WriteBits(bActive, 1); + msg.WriteBits(bAllow, 1); +} + +void hhTractorBeam::ReadFromSnapshot( const idBitMsgDelta &msg ) { + owner.SetSpawnId(msg.ReadBits(32)); + traceTarget.SetSpawnId(msg.ReadBits(32)); + targetID = msg.ReadBits(32); + + bool beamActive = !!msg.ReadBits(1); + if (beamActive != bActive && beamClientPredictTime <= gameLocal.time) { + if (beamActive) { + Activate(); + } + else { + Deactivate(); + } + } + bAllow = !!msg.ReadBits(1); +} + +void hhTractorBeam::SpawnComponents(hhShuttle *shuttle) { + SetOwner(shuttle); + + idVec3 ownerOrigin = owner->GetOrigin(); + idMat3 ownerAxis = owner->GetAxis(); + idDict *ownerArgs = &owner->spawnArgs; + + tractorCost = ownerArgs->GetInt("tractorcost"); + offsetTraceStart = ownerArgs->GetVector("offset_tractortracestart"); + offsetTraceEnd = ownerArgs->GetVector("offset_tractortraceend"); + offsetEquilibrium = ownerArgs->GetVector("offset_tractorequilibrium"); + + float tension = ownerArgs->GetFloat("tension", "1.0"); + float slack; + if (gameLocal.isMultiplayer) { + slack = ownerArgs->GetFloat("slack_mp", "1.0"); + } + else { + slack = ownerArgs->GetFloat("slack", "1.0"); + } + float yawLimit = ownerArgs->GetFloat("yawlimit", "180"); + const char *handName = ownerArgs->GetString("def_tractorhand"); + const char *animName = ownerArgs->GetString("boundanim"); + + if (gameLocal.isClient) { + bindController = static_cast( gameLocal.SpawnClientObject(ownerArgs->GetString("def_bindController"), NULL) ); + } + else { + bindController = static_cast( gameLocal.SpawnObject(ownerArgs->GetString("def_bindController")) ); + } + HH_ASSERT( bindController.IsValid() ); + bindController->fl.networkSync = false; + + bindController->SetTension(tension); + bindController->SetSlack(slack); + bindController->SetRiderParameters(animName, handName, yawLimit, 0.0f); + bindController->SetOrigin(ownerOrigin + offsetEquilibrium * ownerAxis); + bindController->Bind(owner.GetEntity(), true); +// bindController->fl.robustDormant = true; // This beam could be going through walls, so set it to a robust dormancy check + bindController->fl.neverDormant = true; + bindController->SetShuttle(true); + + if (gameLocal.isClient) { + traceController = gameLocal.SpawnClientObject( ownerArgs->GetString("def_traceController"), NULL ); + } + else { + traceController = gameLocal.SpawnObject( ownerArgs->GetString("def_traceController") ); + } + HH_ASSERT( traceController.IsValid() ); + traceController->fl.networkSync = false; + + traceController->SetOrigin(ownerOrigin + offsetTraceEnd * ownerAxis); + traceController->Bind(owner.GetEntity(), true); +// traceController->fl.robustDormant = true; // This beam could be going through walls, so set it to a robust dormancy check + traceController->fl.neverDormant = true; + + idVec3 offsetBeamPosition = ownerArgs->GetVector("offset_tractorbeamposition"); + idVec3 pos = ownerOrigin + offsetBeamPosition * ownerAxis; + beam = hhBeamSystem::SpawnBeam(pos, ownerArgs->GetString("beam_tractor"), mat3_identity, true); + HH_ASSERT( beam.IsValid() ); + beam->SetOrigin(pos); + beam->SetAxis(ownerAxis); + beam->Bind(owner.GetEntity(), false); + beam->Activate( false ); + beam->fl.robustDormant = true; // This beam could be going through walls, so set it to a robust dormancy check + + float feedbacktension = ownerArgs->GetFloat("feedbacktension"); + feedbackForce.SetRestoreFactor(feedbacktension); + float feedbackslack = ownerArgs->GetFloat("feedbackslack"); + feedbackForce.SetRestoreSlack(feedbackslack); +} + +bool hhTractorBeam::Exists() { + return (owner.IsValid() && bindController.IsValid() && traceController.IsValid() && beam.IsValid()); +} + +bool hhTractorBeam::IsAllowed() { + return (Exists() && bAllow && /*!owner->IsDocked() &&*/ owner->HasPower(tractorCost) && owner->noTractorTime <= gameLocal.time); +} + +bool hhTractorBeam::IsActive() { + return (Exists() && bActive); +} + +bool hhTractorBeam::IsLocked() { + return (IsActive() && GetTarget() != NULL); +} + +idEntity *hhTractorBeam::GetTarget() { + if (!bindController.IsValid() || !bindController.GetEntity()) { //rww - in the case of no tractor beam (testing concept for mp), this controller may not exist, so check it. + return NULL; + } + return bindController->GetRider(); +} + +void hhTractorBeam::RequestState(bool wantsActive) { + if (wantsActive && !IsActive() && IsAllowed()) { + Activate(); + } + else if (!wantsActive && IsActive()) { + Deactivate(); + } +} + +void hhTractorBeam::Activate() { + bActive = true; + beamClientPredictTime = gameLocal.time + USERCMD_MSEC*4; + owner->StartSound("snd_tractor", SND_CHANNEL_TRACTORBEAM); + owner->StartSound("snd_tractor_start", SND_CHANNEL_ANY); +} + +void hhTractorBeam::Deactivate() { + if ( !gameLocal.isMultiplayer && GetTarget() && GetTarget()->IsType( hhMonsterAI::Type ) ) { + idVec3 velocity = GetTarget()->GetPhysics()->GetLinearVelocity(); + hhMonsterAI *targetAI = static_cast( GetTarget() ); + if ( targetAI && owner.IsValid() ) { + targetAI->SetEnemy( owner.GetEntity()->GetPilot() ); + } + if ( velocity.Length() > GetTarget()->spawnArgs.GetFloat( "tractor_kill_speed", "800" ) ) { + if ( targetAI ) { + targetAI->Damage( NULL, NULL, idVec3( 0,0,0 ), "damage_monsterfall", 1, INVALID_JOINT ); + if ( targetAI->GetAFPhysics() ) { + targetAI->GetAFPhysics()->SetLinearVelocity( velocity * GetTarget()->spawnArgs.GetFloat( "tractor_speed_scale", "15" ) ); + } + } + } + } + bActive = false; + beamClientPredictTime = gameLocal.time + USERCMD_MSEC*4; + if (beam.IsValid()) { + beam->Activate( false ); + beam->SetTargetEntity(NULL); + } + if (owner.IsValid()) { + owner->StopSound(SND_CHANNEL_TRACTORBEAM); + owner->StartSound("snd_tractor_stop", SND_CHANNEL_ANY); + } + if (GetTarget()) { + GetTarget()->fl.isTractored = false; //rww + } + if (bindController.IsValid()) { + bindController->Detach(); + } + feedbackForce.SetEntity(NULL); +} + +bool hhTractorBeam::ValidTarget(idEntity *ent) { + bool b = ent && + !(ent->IsType(idActor::Type) && static_cast(ent)->InVehicle()) && + ent->GetPhysics() && + ent->GetPhysics()->GetContents() != 0 && + !ent->IsHidden() && + ent->fl.canBeTractored; + + if (gameLocal.isMultiplayer && b) { //rww - if mp and a valid target thus far, perform mp-only check(s) + if (ent->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(ent); + if (pl->IsDead()) { //if he died, let go. + b = false; + } + else if (pl->IsWallWalking()) { //don't allow grabbing wallwalking players in mp + b = false; + } + } + else if (ent->IsType(hhMPDeathProxy::Type)) { //don't grab temporary client-based corpses + b = false; + } + } + + return b; +} + +void hhTractorBeam::ReleaseTarget() { + // Release the target, but stay active + beam->SetTargetEntity(NULL); + if (GetTarget()) { + GetTarget()->fl.isTractored = false; //rww + } + bindController->Detach(); + feedbackForce.SetEntity(NULL); +} + +void hhTractorBeam::LaunchTarget(float speed) { + idEntity *target = bindController->GetRider(); + ReleaseTarget(); + if (target) { + idVec3 vel = owner->GetAxis()[0] * speed; + target->GetPhysics()->SetLinearVelocity(vel); + } +} + +idEntity *hhTractorBeam::TraceForTarget(trace_t &results) { + idVec3 start = owner->GetOrigin() + offsetTraceStart * owner->GetAxis(); + idVec3 end; + if (GetTarget() && results.c.entityNum != ENTITYNUM_NONE) { + end = GetTarget()->GetOrigin(); + } + else { + end = traceController->GetOrigin(); + } + gameLocal.clip.TracePoint( results, start, end, MASK_TRACTORBEAM, owner.GetEntity() ); + + idEntity *validEntity = NULL; + if (results.fraction < 1.0f) { + idEntity *entity = gameLocal.GetTraceEntity(results); + if (ValidTarget(entity)) { + if (entity && (entity == GetTarget() || !entity->fl.isTractored)) { //rww - if i'm not grabbing it already, don't grab it if it's tractored already. + validEntity = entity; + } + } + } + + if (beam.IsValid() && !GetTarget()) { + beam->SetTargetLocation(results.endpos); //rww - in any case, set the beam's target pos to the trace endpos + } + + return validEntity; +} + +void hhTractorBeam::AttachTarget(idEntity *target, trace_t &results) { + if (target->IsType(idAFEntity_Base::Type) && // Active ragdoll + static_cast(target)->IsActiveAF()) { + idAFEntity_Base *af = static_cast(target); + //int joint = CLIPMODEL_ID_TO_JOINT_HANDLE( results.c.id ); + targetID = af->BodyForClipModelId(results.c.id); + targetLocalOffset = vec3_origin; // apply to center of mass + } + else if (target->IsType(idActor::Type)) { // Non-ragdolled actor + targetID = 0; + targetLocalOffset = target->GetPhysics()->GetBounds().GetCenter();// - target->GetOrigin(); + + //rww - for mp kill credits + if (gameLocal.isMultiplayer) { + if (target->IsType(hhPlayer::Type) && owner->GetPilot() && owner->GetPilot()->IsType(hhPlayer::Type)) { + hhPlayer *plTarget = static_cast(target); + plTarget->airAttacker = owner->GetPilot(); + plTarget->airAttackerTime = gameLocal.time + 300; + } + else if (target->IsType(hhSpiritProxy::Type)) { //in mp, snap player back to their body when their prox is tractored + target->Damage(owner.GetEntity(), owner.GetEntity(), vec3_origin, "damage_generic", 0.0f, -1); //call damage, but don't actually do damage. + } + } + } + else { // Everything else + targetID = results.c.id; + targetLocalOffset = ( results.c.point - target->GetOrigin(targetID) ) * target->GetAxis(targetID).Transpose(); + } + bindController->Attach(target, true, targetID, targetLocalOffset); + beam->SetTargetEntity(target, targetID, targetLocalOffset); + beam->SetArcVector(owner->GetAxis()[0]); + feedbackForce.SetEntity(owner.GetEntity()); + + target->fl.isTractored = true; //rww +} + +void hhTractorBeam::Update() { + trace_t results; + + if (Exists()) { + if (IsActive()) { + if (!IsAllowed() || !owner->ConsumePower(tractorCost)) { + // Ran out of power or no longer allowed + Deactivate(); + } + + if (IsLocked()) { + if (!ValidTarget(GetTarget())) { + // Locked target is no longer valid + ReleaseTarget(); + } + else if (gameLocal.isMultiplayer && GetTarget() && GetTarget()->IsType(hhPlayer::Type)) { + //rww - just hint the player in the direction of the shuttle that has them picked up for mp (help with disorientation) + float frametime = ((float)(gameLocal.time - gameLocal.previousTime))/USERCMD_MSEC; + hhPlayer *targetPl = static_cast(GetTarget()); + + + idQuat plOri = targetPl->GetViewAngles().ToQuat(); + idVec3 targetPos = owner->GetOrigin(); + targetPos.z -= 80.0f; + + idVec3 t = (targetPos-targetPl->GetOrigin()); + t.Normalize(); + idQuat idealOri = t.ToMat3().ToQuat(); + + idQuat finalOri; + finalOri.Slerp(plOri, idealOri, 0.02f*frametime); + + targetPl->SetOrientation(targetPl->GetOrigin(), targetPl->GetPhysics()->GetAxis(), finalOri.ToMat3()[0], finalOri.ToAngles()); + } + } + } + + // Do the trace as long as we're not already locked, so the GUI can use it to show the cursor + if (!IsAllowed() || IsLocked()) { + //traceTarget = NULL; + //rww - changed to continue tracing even after grabbing something + + results.c.entityNum = 0; //try tracing to the target + traceTarget = TraceForTarget(results); + if (traceTarget != GetTarget()) { + results.c.entityNum = ENTITYNUM_NONE; //try tracing forward along beam + traceTarget = TraceForTarget(results); + if (traceTarget != GetTarget()) { + ReleaseTarget(); + if (beam.IsValid()) { + beam->SetTargetLocation(results.endpos); + } + } + } + } + else { + traceTarget = TraceForTarget(results); + + if (IsActive()) { + if (traceTarget != NULL) { + AttachTarget(traceTarget.GetEntity(), results); + } + //rww - instead of doing this and giving a misleading trace result, setting to the actual trace.endpos + /* + else { + // No target found, but leave beam out there + beam->SetTargetEntity(traceController.GetEntity()); + } + */ + } + } + + if (IsActive()) { + // Update beam based on shuttle orientation + if (!beam->IsActivated()) { + beam->Activate( true ); + } + beam->SetArcVector( owner->GetAxis()[0] ); + } + + // Update the feedback force + if (IsLocked()) { + idVec3 p = GetTarget()->GetOrigin(targetID) + targetLocalOffset * GetTarget()->GetAxis(targetID); + idVec3 a = owner->GetOrigin() - p; + a.Normalize(); + a *= offsetEquilibrium.Length(); + idVec3 idealPosition = p + a; + feedbackForce.SetTarget(idealPosition); + feedbackForce.Evaluate(gameLocal.time); + } + } +} + + + +//========================================================================== +// +// hhShuttle +// +//========================================================================== +const idEventDef EV_Vehicle_TractorBeam( "tractorBeam" ); +const idEventDef EV_Vehicle_TractorBeamDone( "tractorBeamDone" ); + +CLASS_DECLARATION(hhVehicle, hhShuttle) + EVENT( EV_PostSpawn, hhShuttle::Event_PostSpawn ) + EVENT( EV_Vehicle_FireCannon, hhShuttle::Event_FireCannon ) + EVENT( EV_Vehicle_TractorBeam, hhShuttle::Event_ActivateTractorBeam ) + EVENT( EV_Vehicle_TractorBeamDone, hhShuttle::Event_DeactivateTractorBeam ) +END_CLASS + + +hhShuttle::~hhShuttle() { + if (renderEntity.gui[0]) { + gameLocal.FocusGUICleanup(renderEntity.gui[0]); //HUMANHEAD rww + uiManager->DeAlloc(renderEntity.gui[0]); + renderEntity.gui[0] = NULL; + } +} + +void hhShuttle::Spawn() { + bSputtering = false; + malfunctionThrustFactor = spawnArgs.GetFloat( "malfunctionThrustFactor" ); + + idealTipState = TIP_STATE_NONE; + curTipState = TIP_STATE_NONE; + nextTipChange = gameLocal.time; + + // Fade console in + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // Dir + + teleportDest = GetOrigin() + idVec3(-64, 0, 0) * GetPhysics()->GetAxis(); + teleportAngles = idAngles( 0.0f, GetPhysics()->GetAxis().ToAngles().yaw, 0.0f ); + + terminalVelocitySquared = spawnArgs.GetFloat( "terminalvelocity" ); + terminalVelocitySquared *= terminalVelocitySquared; + + noTractorTime = 0; //rww + + fl.clientEvents = true; //rww - allow events to be posted on client for this entity + + PostEventMS( &EV_PostSpawn, 0 ); + + fl.networkSync = true; +} + +void hhShuttle::Event_PostSpawn() { + // Spawn all sub-entities after initial population for multiplayer's sake + + // Spawn components for tractor beam + const idKeyValue *kv1 = spawnArgs.FindKey("attackFunc"); + const idKeyValue *kv2 = spawnArgs.FindKey("altAttackFunc"); + if ((kv1 && !kv1->GetValue().Icmp("tractorBeam")) || (kv2 && !kv2->GetValue().Icmp("tractorBeam")) ) { + tractor.SpawnComponents( this ); + } + + // Spawn Model based thrusters + const char *thrusterDef = spawnArgs.GetString("def_thruster"); + if (thrusterDef && *thrusterDef) { + // Spawn thrusters with appropriate direction vectors + idVec3 offset; + char suffix[THRUSTER_DIRECTIONS] = { 'F', 'B', 'L', 'R', 'U', 'D' }; + int directions[THRUSTER_DIRECTIONS] = { MASK_POSX, MASK_NEGX, MASK_POSY, MASK_NEGY, MASK_POSZ, MASK_NEGZ }; + for (int i=0; iWriteStaticObject( tractor ); + for (int i=0; iWriteFloat( terminalVelocitySquared ); + savefile->WriteVec3( teleportDest ); + savefile->WriteAngles( teleportAngles ); + savefile->WriteFloat( malfunctionThrustFactor ); + savefile->WriteBool( bSputtering ); + savefile->WriteInt(noTractorTime); + savefile->WriteInt( idealTipState ); + savefile->WriteInt( curTipState ); + savefile->WriteInt( nextTipChange ); +} + +void hhShuttle::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( tractor ); + for (int i=0; iReadFloat( terminalVelocitySquared ); + savefile->ReadVec3( teleportDest ); + savefile->ReadAngles( teleportAngles ); + savefile->ReadFloat( malfunctionThrustFactor ); + savefile->ReadBool( bSputtering ); + savefile->ReadInt(noTractorTime); + savefile->ReadInt( idealTipState ); + savefile->ReadInt( curTipState ); + savefile->ReadInt( nextTipChange ); +} + +void hhShuttle::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhVehicle::WriteToSnapshot(msg); + + /* + int i; + for (i = 0; i < THRUSTER_DIRECTIONS; i++) { + msg.WriteBits(thrusters[i].GetSpawnId(), 32); + } + */ + + msg.WriteFloat(renderEntity.shaderParms[4]); + msg.WriteFloat(renderEntity.shaderParms[5]); + + msg.WriteBits(bSputtering, 1); + msg.WriteBits(noTractorTime, 32); + + tractor.WriteToSnapshot(msg); +} + +void hhShuttle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhVehicle::ReadFromSnapshot(msg); + + /* + int i; + for (i = 0; i < THRUSTER_DIRECTIONS; i++) { + int spawnId = msg.ReadBits(32); + if (!spawnId) { + thrusters[i] = NULL; + } + else { + thrusters[i].SetSpawnId(spawnId); + } + } + */ + + SetShaderParm(4, msg.ReadFloat()); + SetShaderParm(5, msg.ReadFloat()); + + bSputtering = !!msg.ReadBits(1); + noTractorTime = msg.ReadBits(32); + + tractor.ReadFromSnapshot(msg); +} + +void hhShuttle::ClientPredictionThink( void ) { + if (fl.hidden) { + return; + } + + Think(); + + UpdateVisuals(); + Present(); +} + + +// These for model based thrusters ------------------------------------------------ + +hhVehicleThruster *hhShuttle::SpawnThruster(idVec3 &offset, idVec3 &dir, const char *thrusterName, bool master) { + idVec3 pos = GetOrigin() + offset * GetAxis(); + idVec3 direction = dir * GetAxis(); + idMat3 axis = direction.ToMat3(); + + idDict args; + args.SetVector("origin", pos); + args.SetBool("soundmaster", master); + hhVehicleThruster *thruster; + if (gameLocal.isClient) { + thruster = static_cast(gameLocal.SpawnClientObject(thrusterName, &args)); + } + else { + thruster = static_cast(gameLocal.SpawnObject(thrusterName, &args)); + } + if (thruster) { + thruster->fl.networkSync = false; + + thruster->SetOrigin(pos); + thruster->SetAxis(axis); + thruster->Bind(this, true); + thruster->SetOwner(this); + thruster->Hide(); + thruster->SetSmoker(master, offset, dir); + } + return thruster; +} + +void hhShuttle::FireThrusters( const idVec3& impulse ) { + idVec3 curVel = physicsObj.GetLinearVelocity(); + idVec3 excess( vec3_zero ); + idVec3 Iu( vec3_zero ); + idVec3 Vu( vec3_zero ); + float impulseLength = 0.0f; + idVec3 finalImpulse = impulse; + + // Subtract portion of impulse out that is in direction of current terminal velocity + //FIXME: not sure if this should be in the physics code + if( !IsNoClipping() && curVel.LengthSqr() > terminalVelocitySquared ) { + Iu = finalImpulse; + impulseLength = Iu.Normalize(); + Vu = curVel; + Vu.Normalize(); + excess = (impulseLength * (Iu * Vu)) * Vu; + finalImpulse -= excess; + } + + // Apply wacky controls when dying + if (health <= 0) { + finalImpulse += hhUtils::RandomVector() * 127.0f * malfunctionThrustFactor; + } + + if (finalImpulse.Length() > VECTOR_EPSILON) { + ConsumePower(thrusterCost); + } + + // Play sputter sound if trying to move without enough power + if (!HasPower(thrusterCost)) { + if (finalImpulse != vec3_origin && !bSputtering) { + StartSound("snd_sputter", SND_CHANNEL_ANY); + bSputtering = true; + } + else { + bSputtering = false; + } + + finalImpulse.Zero(); + } + + ApplyImpulse( finalImpulse ); +} + +void hhShuttle::ApplyBoost( float magnitude ) { + ApplyImpulse( GetAxis()[0] * magnitude ); +} + +// Update all tick based effects +void hhShuttle::UpdateEffects( const idVec3& localThrust ) { + // Update thrusters on/off state based on direction of velocity vector + int directionMask = localThrust.DirectionMask(); + if ( GetPilot() && GetPilot()->IsType( idAI::Type ) ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + if ( -GetAxis()[2] * vel > 0 ) { + thrusters[THRUSTER_TOP]->SetThruster( true ); + thrusters[THRUSTER_TOP]->Update( localThrust ); + } else { + thrusters[THRUSTER_TOP]->SetThruster( false ); + thrusters[THRUSTER_TOP]->Update( localThrust ); + } + if ( GetAxis()[2] * vel > 0 ) { + thrusters[THRUSTER_BOTTOM]->SetThruster( true ); + thrusters[THRUSTER_BOTTOM]->Update( localThrust ); + } else { + thrusters[THRUSTER_BOTTOM]->SetThruster( false ); + thrusters[THRUSTER_BOTTOM]->Update( localThrust ); + } + if ( GetAxis()[1] * vel > 0 ) { + thrusters[THRUSTER_RIGHT]->SetThruster( true ); + thrusters[THRUSTER_RIGHT]->Update( localThrust ); + } else { + thrusters[THRUSTER_RIGHT]->SetThruster( false ); + thrusters[THRUSTER_RIGHT]->Update( localThrust ); + } + if ( -GetAxis()[1] * vel > 0 ) { + thrusters[THRUSTER_LEFT]->SetThruster( true ); + thrusters[THRUSTER_LEFT]->Update( localThrust ); + } else { + thrusters[THRUSTER_LEFT]->SetThruster( false ); + thrusters[THRUSTER_LEFT]->Update( localThrust ); + } + } else { + for (int thrusterIndex=0; thrusterIndexSetThruster( INDEX_IN_MASK(directionMask, thrusterIndex) ); + thrusters[thrusterIndex]->Update( localThrust ); + } + } + } +} + +// Static function to determine if a pilot is suitable +bool hhShuttle::ValidPilot( idActor *act ) { + if (act && act->health > 0) { + if (act->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(act); + if( !player->IsSpiritOrDeathwalking() && !player->IsPossessed() ) { + return true; + } + } + else if (act->IsType(idAI::Type)) { + idAI *ai = static_cast(act); + if (ai->spawnArgs.GetBool("canPilotShuttle")) { + return true; + } + } + } + return false; +} + +bool hhShuttle::WillAcceptPilot( idActor *act ) { + return IsConsole() && hhShuttle::ValidPilot( act ) && CanBecomeVehicle(act); +} + +void hhShuttle::AcceptPilot( hhPilotVehicleInterface* pilotInterface ) { + hhVehicle::AcceptPilot( pilotInterface ); + + // Fade the console out + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, 1.0f); // dir + + // Alert the hud that control has been taken (currently unused) + if (renderEntity.gui[0]) { + renderEntity.gui[0]->Trigger(gameLocal.time); + } +} + +void hhShuttle::EjectPilot() { + + // Fade ship out + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // dir + + tractor.RequestState(false); + + for (int ix=0; ixSetDying(false); + thrusters[ix]->SetThruster(false); + thrusters[ix]->Update(vec3_origin); + } + } + + hhVehicle::EjectPilot(); +} + +void hhShuttle::ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ) { + hhVehicle::ProcessPilotInput( cmds, viewAngles ); + + if( cmds && IsVehicle() ) { + UpdateEffects( idVec3(cmds->forwardmove, -cmds->rightmove, cmds->upmove) ); + } +} + +/* +bool hhShuttle::UsesCrosshair() const { + return (!IsDocked() || dock->AllowsFiring()) && hhVehicle::UsesCrosshair(); +} +*/ + +void hhShuttle::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) { + for(int ix=0; ixSetDying(true); + } + } + + hhVehicle::Killed(inflictor, attacker, damage, dir, location); +} + +void hhShuttle::DrawHUD( idUserInterface* _hud ) { + if( !_hud ) { + return; + } + + float spawnPower = spawnArgs.GetInt( "maxPower" ); + bool bDocked = dock.IsValid(); + bool bDockedInTransporter = bDocked && dock->IsType(hhShuttleTransport::Type); + bool bDockLocked = bDocked && dock->IsLocked(); + bool bNeedsRecharge = (health < spawnHealth) || (currentPower < spawnPower); + _hud->SetStateBool( "docked", bDocked ); + _hud->SetStateBool( "dockedtransporter", bDockedInTransporter ); + _hud->SetStateBool( "docklocked", bDockLocked ); + _hud->SetStateBool( "recharging", bNeedsRecharge && bDocked && dock->Recharges() ); + _hud->SetStateBool( "crosshair", fireController && fireController->UsesCrosshair() ); + _hud->SetStateBool( "lowhealth", health/(float)spawnHealth < 0.20f ); + _hud->SetStateBool( "lowpower", currentPower/spawnPower < 0.20f ); + + float velfrac = physicsObj.GetLinearVelocity().LengthSqr() / terminalVelocitySquared; +// float velfrac = controller.GetThrustBooster(); + velfrac = idMath::ClampFloat( 0.0f, 1.0f, velfrac ); + _hud->SetStateFloat( "velocityfraction", velfrac ); + + bool bHasTractor = tractor.Exists(); + bool bTractorActive = tractor.IsActive(); + bool bTractorLocked = tractor.IsLocked(); + bool bTractorSighted = tractor.IsAllowed() && (tractor.GetTraceTarget() != NULL); + bool bTractorAllowed = tractor.IsAllowed(); + _hud->SetStateBool( "hastractor", bHasTractor ); + _hud->SetStateBool( "tractoractive", bTractorActive ); + _hud->SetStateBool( "tractorsighted", bTractorSighted ); + _hud->SetStateBool( "tractorlocked", bTractorLocked ); + _hud->SetStateBool( "tractorallowed", bTractorAllowed ); + + // HUMANHEAD PCF pdm 05-18-06: Removed non-octal mass from shuttle hud +/* if( bHasTractor && bTractorLocked ) { + float mass = tractor.GetTarget()->GetPhysics()->GetMass(); + _hud->SetStateInt("tractormass", (int) mass); + const char *massFormatter = common->GetLanguageDict()->GetString("#str_41161"); + _hud->SetStateString("tractormasstext", va(massFormatter, (int)mass)); + }*/ + // HUMANHEAD END + + if (GetPilot() && GetPilot()->IsType(hhPlayer::Type)) { + + if (!g_tips.GetBool()) { + idealTipState = TIP_STATE_NONE; + nextTipChange = gameLocal.time; + } + + // Transition to desired tip state, always passing through TIP_STATE_NONE + if ( gameLocal.time >= nextTipChange && idealTipState != curTipState ) { + + if (curTipState != TIP_STATE_NONE) { // Turn off old tip + _hud->HandleNamedEvent( "shuttleTipWindowDown" ); + nextTipChange = gameLocal.time + 300; + curTipState = TIP_STATE_NONE; + } + else { // Turn on new tip + const char *tip = NULL; + switch(idealTipState) { + case TIP_STATE_NONE: + break; + case TIP_STATE_DOCKED: + gameLocal.SetTip(_hud, NULL, "", NULL, NULL, "tip1"); + tip = spawnArgs.GetString("text_exittip"); + gameLocal.SetTip(_hud, "_attackalt", tip, NULL, NULL, "tip2"); + _hud->HandleNamedEvent( "shuttleTipWindowUp" ); + curTipState = idealTipState; + break; + case TIP_STATE_UNDOCKED: + tip = spawnArgs.GetString("text_cannontip"); + gameLocal.SetTip(_hud, "_attack", tip, NULL, NULL, "tip1"); + tip = spawnArgs.GetString("text_tractortip"); + gameLocal.SetTip(_hud, "_attackalt", tip, NULL, NULL, "tip2"); + _hud->HandleNamedEvent( "shuttleTipWindowUp" ); + curTipState = idealTipState; + + // Go away in a little while + idealTipState = TIP_STATE_NONE; + nextTipChange = gameLocal.time + 3000; + break; + } + } + + } + } + + _hud->StateChanged(gameLocal.time); + hhVehicle::DrawHUD( _hud ); +} + +void hhShuttle::SetDock( const hhDock* dock ) { + hhVehicle::SetDock(dock); + + bool bDocked = (dock != NULL); + bool bDockedInTransporter = bDocked && dock->IsType(hhShuttleTransport::Type); + + if (bDocked && !bDockedInTransporter) { + // Just entered dock, display tip + idealTipState = TIP_STATE_DOCKED; + nextTipChange = gameLocal.time; + + // Just entered dock, set this one as my respawn point + idVec3 location = dock->GetOrigin() + dock->spawnArgs.GetVector("offset_console") * dock->GetAxis(); + teleportDest = location + idVec3(-64, 0, 0) * GetPhysics()->GetAxis(); + teleportAngles = idAngles( 0.0f, dock->GetAxis().ToAngles().yaw, 0.0f ); + } +} + +void hhShuttle::Undock() { + hhVehicle::Undock(); + + // Just left dock, display tip + idealTipState = TIP_STATE_UNDOCKED; + nextTipChange = gameLocal.time; +} + +void hhShuttle::Ticker() { + // Handle noclipping + physicsObj.SetContents(IsNoClipping() ? 0 : vehicleContents ); + physicsObj.SetClipMask(IsNoClipping() ? 0 : vehicleClipMask ); + + if( IsVehicle() && IsDocked() ) { + dock->UpdateAxis( GetAxis() ); + } + + tractor.Update(); +} + +void hhShuttle::RemoveVehicle() { + if (IsDocked()) { + dock->ShuttleExit(this); + dock = NULL; + } + GetPhysics()->SetGravity( vec3_origin ); // so shuttle doesn't bounce on ground and make noise + hhVehicle::RemoveVehicle(); +} + +void hhShuttle::SetConsoleModel() { + hhVehicle::SetConsoleModel(); + + // Fade console in + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, -1.0f); // Dir + // Need to add gui back(SetModel strips it) + if (!renderEntity.gui[0]) { //rww - don't double up guis + renderEntity.gui[0] = uiManager->FindGui(spawnArgs.GetString("gui"), true, true); + } +} + +void hhShuttle::SetVehicleModel() { + hhVehicle::SetVehicleModel(); + + // Fade shuttle in + SetShaderParm(4, -MS2SEC(gameLocal.time)); // time + SetShaderParm(5, 1.0f); // Dir + validThrustTime = gameLocal.time + spawnArgs.GetInt("delay_thrust"); +} + +void hhShuttle::RecoverFromDying() { + StopSound(SND_CHANNEL_DYING); + CancelEvents(&EV_VehicleExplode); + if (domelight.IsValid()) { + domelight->SetLightParm(7, 0.0f); + } + for(int ix=0; ixSetDying(false); + } + } +} + +void hhShuttle::PerformDeathAction(int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir) { + switch(deathAction) { + case 2: // Teleport pilot + if (!gameLocal.isMultiplayer) { //rww - fall through on purpose + if (savedPilot->IsType(hhPlayer::Type)) { + idVec3 origin; + idMat3 axis; + idVec3 viewDir; + idAngles angles; + idMat3 eyeAxis; + static_cast(savedPilot)->GetResurrectionPoint( origin, axis, viewDir, angles, eyeAxis, savedPilot->GetPhysics()->GetAbsBounds(), teleportDest, teleportAngles.ToMat3(), teleportAngles.ToForward(), teleportAngles ); + static_cast(savedPilot)->DeathWalk( origin, axis, viewDir.ToMat3(), angles, eyeAxis ); + } + else { + savedPilot->Teleport(teleportDest, teleportAngles, NULL); + } + break; + } + default: + hhVehicle::PerformDeathAction(deathAction, savedPilot, attacker, dir); + break; + } +} + +void hhShuttle::InitiateRecharging() { +} + +void hhShuttle::FinishRecharging() { + if (GetPilot() != NULL) { + fl.takedamage = true; + } +} + +void hhShuttle::Event_FireCannon() { + if (IsDocked() && GetPilot() && GetPilot()->IsType(idAI::Type) && !dock->AllowsFiring()) { + // Let monsters boost out of docks a bit + ApplyBoost( 127.0f * 0.25f ); + return; + } + + if( IsDocked() && spawnArgs.GetBool("dockBoost") && !dock->AllowsFiring() ) { + ApplyBoost( 127.0f * dockBoostFactor ); + return; + } + if( spawnArgs.GetBool("tractorlaunch") && GetTractorTarget() ) { + LaunchTractorTarget(spawnArgs.GetFloat("launch_speed")); + bDisallowAttackUntilRelease = true; + bDisallowAltAttackUntilRelease = true; + return; + } + if ( !IsDocked() || dock->AllowsFiring()) { + hhVehicle::Event_FireCannon(); + } +} + +void hhShuttle::Event_ActivateTractorBeam() { + tractor.RequestState( true ); +} + +void hhShuttle::Event_DeactivateTractorBeam() { + tractor.RequestState( false ); +} diff --git a/neo/prey/Prey/game_shuttle.h b/neo/prey/Prey/game_shuttle.h new file mode 100644 index 00000000..07f7daac --- /dev/null +++ b/neo/prey/Prey/game_shuttle.h @@ -0,0 +1,152 @@ +#ifndef __GAME_SHUTTLE_H__ +#define __GAME_SHUTTLE_H__ + +class hhBindController; +class hhShuttle; + +#define TIP_STATE_NONE 0 +#define TIP_STATE_DOCKED 1 +#define TIP_STATE_UNDOCKED 2 + +//========================================================================== +// +// hhTractorBeam +// +//========================================================================== + +class hhTractorBeam : public idClass { + CLASS_PROTOTYPE( hhTractorBeam ); + + friend hhShuttle; + +public: + hhTractorBeam(); + void SpawnComponents( hhShuttle *shuttle ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + bool Exists(); + bool IsAllowed(); + bool IsActive(); + bool IsLocked(); + + idEntity * GetTarget(); + void SetAllow( bool allow ) { bAllow = allow; } + void Update(); + idEntity * GetTraceTarget() { return traceTarget.GetEntity(); } + void RequestState( bool wantsActive ); + idEntity * TraceForTarget( trace_t &results ); + void LaunchTarget(float speed); + +protected: + void SetOwner( hhShuttle *shuttle ) { owner = shuttle; } + void Activate(); + void Deactivate(); + void AttachTarget( idEntity *ent, trace_t &results ); + bool ValidTarget( idEntity *ent ); + void ReleaseTarget(); + +protected: + idEntityPtr owner; + idEntityPtr beam; + idEntityPtr traceTarget; // Results of target trace this frame + idEntityPtr bindController; // Bind controller for tractor beam + idEntityPtr traceController; // Point for traces + hhForce_Converge feedbackForce; // feedback force applied to shuttle + int tractorCost; // Power cost of tractor at 10 Hz + bool bAllow; // false if tractor beam is not allowed + bool bActive; // whether beam is active + int beamClientPredictTime; //rww - don't flicker the beam due to snapshot timings + idVec3 offsetTraceStart; // Offset to start of trace + idVec3 offsetTraceEnd; // Offset to end of trace + idVec3 offsetEquilibrium; // Offset to equilibrium point for targets + idVec3 targetLocalOffset; // Offset to attach point in entity local coordinates + int targetID; // Body ID of target entity +}; + +//========================================================================== +// +// hhShuttle +// +//========================================================================== + +class hhShuttle : public hhVehicle { + CLASS_PROTOTYPE( hhShuttle ); + + friend hhTractorBeam; + +public: + virtual ~hhShuttle(); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + idEntity * GetTractorTarget() { return tractor.GetTarget(); } + void ReleaseTractorTarget() { tractor.ReleaseTarget(); } + void AllowTractor( bool allow ) { tractor.SetAllow(allow); } + virtual bool TractorIsActive(void) { return tractor.IsActive(); } //rww + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void PerformDeathAction( int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir ); + void LaunchTractorTarget(float mag) { tractor.LaunchTarget(mag); } + + // Static pilot assessor, for queries when you don't have a vehicle instantiated + static bool ValidPilot( idActor *act ); + + virtual bool WillAcceptPilot( idActor *act ); + virtual void ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ); + virtual void AcceptPilot( hhPilotVehicleInterface* pilotInterface ); + virtual void EjectPilot(); + + virtual void FireThrusters( const idVec3& impulse ); + void ApplyBoost( float magnitude ); + + virtual void DrawHUD( idUserInterface* _hud ); + + virtual void RemoveVehicle(); + + virtual void InitiateRecharging(); + virtual void FinishRecharging(); + virtual void SetDock( const hhDock* dock ); + virtual void Undock(); + void RecoverFromDying(); + + int noTractorTime; //HUMANHEAD rww +protected: + virtual void Ticker(); + hhVehicleThruster * SpawnThruster( idVec3 &offset, idVec3 &dir, const char *thrusterName, bool master ); + void UpdateEffects( const idVec3& impulse ); + + virtual void SetConsoleModel(); + virtual void SetVehicleModel(); + +protected: + void Event_PostSpawn(); + virtual void Event_FireCannon(); + void Event_ActivateTractorBeam(); + void Event_DeactivateTractorBeam(); + +protected: + hhTractorBeam tractor; + idEntityPtr thrusters[ THRUSTER_DIRECTIONS ]; + float terminalVelocitySquared; // Maximum speed of vehicle + idVec3 teleportDest; // teleport destination upon death + idAngles teleportAngles; // teleport destination upon death + float malfunctionThrustFactor; + bool bSputtering; + int idealTipState; // desired tip state + int curTipState; // current tip state + int nextTipChange; // next time a tip change can happen +}; + +#endif diff --git a/neo/prey/Prey/game_shuttledock.cpp b/neo/prey/Prey/game_shuttledock.cpp new file mode 100644 index 00000000..01f3fba5 --- /dev/null +++ b/neo/prey/Prey/game_shuttledock.cpp @@ -0,0 +1,454 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhShuttleDock +// +//----------------------------------------------------------------------- +const idEventDef EV_TrySpawnConsole("", NULL); + +CLASS_DECLARATION(hhDock, hhShuttleDock) + EVENT( EV_TrySpawnConsole, hhShuttleDock::Event_SpawnConsole) + EVENT( EV_Remove, hhShuttleDock::Event_Remove) + EVENT( EV_PostSpawn, hhShuttleDock::Event_PostSpawn ) +END_CLASS + +hhShuttleDock::hhShuttleDock() { + dockingBeam = NULL; + dockedShuttle = NULL; + shuttleCount = 0; + lastConsoleAttempt = 0; + bLocked = false; + bPlayingRechargeSound = false; +} + +void hhShuttleDock::Spawn() { + + amountHealth = spawnArgs.GetInt("amounthealth"); + amountPower = spawnArgs.GetInt("amountpower"); + + bCanExitLocked = spawnArgs.GetBool("canExitLocked"); + bLockOnEntry = spawnArgs.GetBool("lockOnEntry"); + offsetNozzle = spawnArgs.GetVector("offset_nozzle"); + offsetConsole = spawnArgs.GetVector("offset_console"); + offsetShuttlePoint = spawnArgs.GetVector("offset_shuttlepoint"); + maxDistance = spawnArgs.GetFloat("maxdistance"); + + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); + dockingForce.SetTarget(GetOrigin() + offsetShuttlePoint * GetAxis()); + + if (maxDistance) { + float shuttleMass = dockedShuttle->GetPhysics()->GetMass(); + float weaponKick = dockedShuttle->GetWeaponRecoil(); + float fireRate = dockedShuttle->GetWeaponFireDelay(); + float shuttleThrust = dockedShuttle->GetThrustFactor() + (weaponKick / 128.0f * fireRate); + float restorationFactor = 128.0f * shuttleMass * shuttleThrust / maxDistance; + restorationFactor *= 60.0f; // hack factor to allow for cannon recoil and inaccuracy + dockingForce.SetRestoreFactor(restorationFactor); + } + + GetPhysics()->SetContents(CONTENTS_SOLID); + + // Start with dock inactive (faded in) + SetShaderParm(4, -MS2SEC(gameLocal.time-10000)); + SetShaderParm(5, -1); + + fl.clientEvents = true; + + CancelEvents(&EV_PostSpawn); // hhDock may have already posted one + PostEventMS( &EV_PostSpawn, 0 ); + + fl.networkSync = true; //rww +} + +void hhShuttleDock::Event_PostSpawn() { + dockingBeam = SpawnDockingBeam(offsetNozzle); + if (!gameLocal.isClient) { + hhDock::Event_PostSpawn(); + } +} + +void hhShuttleDock::Save(idSaveGame *savefile) const { + savefile->WriteVec3( offsetNozzle ); + savefile->WriteVec3( offsetConsole ); + savefile->WriteVec3( offsetShuttlePoint ); + dockedShuttle.Save( savefile ); + dockingBeam.Save( savefile ); + savefile->WriteStaticObject( dockingForce ); + savefile->WriteInt( shuttleCount ); + savefile->WriteInt( lastConsoleAttempt ); + savefile->WriteInt( amountHealth ); + savefile->WriteInt( amountPower ); + savefile->WriteFloat( maxDistance ); + savefile->WriteBool( bLocked ); + savefile->WriteBool( bLockOnEntry ); + savefile->WriteBool( bCanExitLocked ); + savefile->WriteBool( bPlayingRechargeSound ); +} + +void hhShuttleDock::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( offsetNozzle ); + savefile->ReadVec3( offsetConsole ); + savefile->ReadVec3( offsetShuttlePoint ); + dockedShuttle.Restore( savefile ); + dockingBeam.Restore( savefile ); + savefile->ReadStaticObject( dockingForce ); + savefile->ReadInt( shuttleCount ); + savefile->ReadInt( lastConsoleAttempt ); + savefile->ReadInt( amountHealth ); + savefile->ReadInt( amountPower ); + savefile->ReadFloat( maxDistance ); + savefile->ReadBool( bLocked ); + savefile->ReadBool( bLockOnEntry ); + savefile->ReadBool( bCanExitLocked ); + savefile->ReadBool( bPlayingRechargeSound ); +} + +void hhShuttleDock::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(dockingZone.GetSpawnId(), 32); + msg.WriteBits(dockedShuttle.GetSpawnId(), 32); + //msg.WriteBits(dockingBeam.GetSpawnId(), 32); + bool dockingBeamActive = false; + if (dockingBeam.IsValid()) { + dockingBeamActive = dockingBeam->IsActivated(); + } + msg.WriteBits(dockingBeamActive, 1); +} + +void hhShuttleDock::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int spawnId; + + spawnId = msg.ReadBits(32); + if (!spawnId) { + dockingZone = NULL; + } + else { + dockingZone.SetSpawnId(spawnId); + } + + idEntityPtr newShuttle; + + spawnId = msg.ReadBits(32); + if (!spawnId) { + newShuttle = NULL; + } + else { + newShuttle.SetSpawnId(spawnId); + } + + if (newShuttle != dockedShuttle) { + if (dockedShuttle.IsValid()) { + DetachShuttle(dockedShuttle.GetEntity()); + dockedShuttle = NULL; + } + if (newShuttle.IsValid() && !newShuttle->IsHidden()) { + AttachShuttle(newShuttle.GetEntity()); + } + } + + bool dockingBeamActive = !!msg.ReadBits(1); + if (dockingBeam.IsValid()) { + if (dockingBeamActive != dockingBeam->IsActivated()) { + dockingBeam->Activate(dockingBeamActive); + } + } +} + +void hhShuttleDock::ClientPredictionThink( void ) { + //hhDock::ClientPredictionThink(); + if (dockedShuttle.IsValid() && dockedShuttle.GetEntity()) { //hax + BecomeActive(TH_THINK); + } + else { + BecomeInactive(TH_THINK); + } + Think(); +} + +void hhShuttleDock::SpawnConsole() { + idVec3 location = GetOrigin() + offsetConsole * GetAxis(); + + StartSound("snd_spawn", SND_CHANNEL_ANY); + + if ( !gameLocal.isClient ) { + idDict args; + args.SetVector("origin", location); + args.SetMatrix("rotation", GetAxis()); + args.Set("startDock", name.c_str()); + if (gameLocal.isMultiplayer) { //rww + const char *shuttleDef = spawnArgs.GetString("def_shuttle_mp"); + if (shuttleDef && shuttleDef[0]) { + gameLocal.SpawnObject(shuttleDef, &args); + return; + } + } + gameLocal.SpawnObject(spawnArgs.GetString("def_shuttle"), &args); + } +} + +hhBeamSystem *hhShuttleDock::SpawnDockingBeam(idVec3 &offset) { + idVec3 pos = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis(); + hhBeamSystem *beam = hhBeamSystem::SpawnBeam(pos, spawnArgs.GetString("beam_docking"), mat3_identity, true); + assert(beam); + beam->fl.networkSync = false; + beam->SetOrigin(pos); + beam->SetAxis( GetPhysics()->GetAxis()[2].ToMat3() ); + beam->Bind(this, false); + beam->Activate( false ); + return beam; +} + +bool hhShuttleDock::ValidEntity(idEntity *ent) { + if (ent->IsType(hhPlayer::Type)) { + int junk=0; + } + if (gameLocal.isMultiplayer) { //rww + return ( !idStr::Icmp(ent->GetEntityDefName(), spawnArgs.GetString("def_shuttle_mp")) && !ent->IsHidden() ); + } + return ( !idStr::Icmp(ent->GetEntityDefName(), spawnArgs.GetString("def_shuttle")) && !ent->IsHidden() ); +} + +void hhShuttleDock::EntityEntered(idEntity *ent) { + if (ent->IsType(hhShuttle::Type) && !dockedShuttle.IsValid()) { + shuttleCount++; + hhShuttle *shuttle = static_cast(ent); + if (shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + } +} + +void hhShuttleDock::EntityEncroaching(idEntity *ent) { + // If we contain a pilot, but no shuttle: spawn one + if (ent->IsType(idActor::Type) && !static_cast(ent)->InVehicle() && shuttleCount <= 0) { + if (gameLocal.time > lastConsoleAttempt) { + PostEventMS(&EV_TrySpawnConsole, 0); + lastConsoleAttempt = gameLocal.time + 100; + } + } + + else if (ent->IsType(hhShuttle::Type)) { + hhShuttle *shuttle = static_cast(ent); + + // Check for any shuttles that just became valid + if (!dockedShuttle.IsValid() && shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + + // Recharge docked shuttles + if (dockedShuttle == shuttle) { + if( shuttle->IsConsole() ) { + DetachShuttle( shuttle ); + } else { + shuttle->RecoverFromDying(); // Recover every time, just to be sure + if (shuttle->IsDamaged() || shuttle->NeedsPower()) { + + //HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow + if(USERCMD_HZ == 30) { + shuttle->GiveHealth(2*amountHealth); + shuttle->GivePower(2*amountPower); + } else { + shuttle->GiveHealth(amountHealth); + shuttle->GivePower(amountPower); + } + + if (!bPlayingRechargeSound) { + StartSound("snd_recharge", SND_CHANNEL_RECHARGE); + bPlayingRechargeSound = true; + } + } + else { + if (bPlayingRechargeSound) { + StopSound(SND_CHANNEL_RECHARGE); + bPlayingRechargeSound = false; + } + } + } + } + } +} + +void hhShuttleDock::EntityLeaving(idEntity *ent) { + if (ent->IsType(hhShuttle::Type)) { + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); + if (dockedShuttle == ent && !IsLocked()) { + DetachShuttle(dockedShuttle.GetEntity()); + } + } +} + +void hhShuttleDock::AttachShuttle(hhShuttle *shuttle) { + if (!dockedShuttle.IsValid()) { + assert(!shuttle->IsHidden()); + + dockedShuttle = shuttle; + dockingForce.SetEntity(dockedShuttle.GetEntity()); + dockingBeam->SetTargetEntity(dockedShuttle.GetEntity(), 0, dockedShuttle->spawnArgs.GetVector("offset_dockingpoint")); + dockingBeam->Activate( true ); + dockedShuttle->SetDock( this ); + + if (amountHealth || amountPower) { + if ( shuttle->GetPilot() && !shuttle->GetPilot()->IsType( idAI::Type ) ) { + dockedShuttle->InitiateRecharging(); + } + } + + StartSound("snd_looper", SND_CHANNEL_DOCKED); + + if (bLockOnEntry) { + Lock(); + } + + if (spawnArgs.GetBool("nonsolidwhenactive")) { + GetPhysics()->SetContents(0); + } + + ActivateTargets( shuttle ); // Fire triggers + BecomeActive(TH_THINK); + + // Dock is active, make dock fade out + SetShaderParm(4, -MS2SEC(gameLocal.time)); + SetShaderParm(5, 1); + } +} + +void hhShuttleDock::DetachShuttle(hhShuttle *shuttle) { + assert(dockingBeam.IsValid()); + assert(shuttle); + + dockingForce.SetEntity(NULL); + dockingBeam->SetTargetEntity(NULL); + dockingBeam->Activate( false ); + shuttle->Undock(); + + shuttle->FinishRecharging(); + dockedShuttle = NULL; + StopSound(SND_CHANNEL_DOCKED); + StopSound(SND_CHANNEL_RECHARGE); + bPlayingRechargeSound = false; + Unlock(); // Shouldn't get in here when locked except when a shuttle dies within + BecomeInactive(TH_THINK); // Keep thinking so the restore position is updated + + if (spawnArgs.GetBool("nonsolidwhenactive")) { + GetPhysics()->SetContents(CONTENTS_SOLID); + } + + // Dock is inactive, make dock fade in + SetShaderParm(4, -MS2SEC(gameLocal.time)); + SetShaderParm(5, -1); +} + +void hhShuttleDock::Think() { + hhDock::Think(); + if (thinkFlags & TH_THINK) { + // Apply docking force to shuttle if docked, even if not in zone + assert(dockedShuttle == dockingForce.GetEntity()); + idVec3 target = GetOrigin() + offsetShuttlePoint * GetAxis(); + dockingForce.SetTarget(target); + dockingForce.Evaluate(gameLocal.time); + + if (maxDistance) { + // Drop the player if forced outside of our threshold + float threshold = maxDistance * 1.5f; + if ( (dockingForce.GetEntity()->GetOrigin() - target).LengthSqr() >= threshold*threshold) { + // exit + dockedShuttle->EjectPilot(); + } + } + } +} + +void hhShuttleDock::ShuttleExit(hhShuttle *shuttle) { + DetachShuttle(shuttle); + + // NOTE: Since zones don't get exit messages for entities that are destroyed, this is needed to + // decrease the shuttle count manually. + // However, since the shuttle physics shrinks when released, it's possible to get "nearly" outside + // the zone before releasing, in which case the exit DOES get called, screwing up the count. + // So... we bound the count to zero. + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); +} + +bool hhShuttleDock::AllowsExit() { + return !bLocked || CanExitLocked(); +} + +void hhShuttleDock::Lock() { + bLocked = true; + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce_locked")); +} + +void hhShuttleDock::Unlock() { + bLocked = false; + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); +} + +void hhShuttleDock::Event_SpawnConsole() { + if ( !spawnArgs.GetBool( "consolespawn" ) ) { + return; + } + + // Check to make sure there's room + idEntity *touch[ MAX_GENTITIES ]; + idBounds bounds, localBounds; + + idVec3 location = GetOrigin() + offsetConsole * GetAxis(); + localBounds[0] = spawnArgs.GetVector("consoleMins"); + localBounds[1] = spawnArgs.GetVector("consoleMaxs"); + + if ( GetAxis().IsRotated() ) { + bounds.FromTransformedBounds( localBounds, location, GetAxis() ); + } + else { + bounds[0] = localBounds[0] + location; + bounds[1] = localBounds[1] + location; + } + + int num = gameLocal.clip.EntitiesTouchingBounds( bounds, CLIPMASK_VEHICLE, touch, MAX_GENTITIES ); + bool blocked = false; + for (int i=0; iGetPhysics()->GetContents() & CLIPMASK_VEHICLE)) { + blocked = true; + break; + } + } + + if (blocked) { + // Now do more expensive check to see if there's really something blocking us. + // All the EntitiesTouchingBounds() check does is check the extents. + // Make bounds into clip model, and use to test contents at our spawn location + idTraceModel trm; + trm.SetupBox( localBounds ); + idClipModel *clipModel = new idClipModel( trm ); + int contents = gameLocal.clip.Contents(location, clipModel, GetAxis(), CLIPMASK_VEHICLE, this); + delete clipModel; + blocked = contents != 0; + } + + if (!blocked) { + SpawnConsole(); + } +} + +void hhShuttleDock::Event_Remove() { + if (IsLocked()) { + Unlock(); + } + if (dockedShuttle.IsValid()) { + DetachShuttle(dockedShuttle.GetEntity()); + } + hhDock::Event_Remove(); +} + +hhShuttle *hhShuttleDock::GetDockedShuttle(void) { + if (!dockedShuttle.IsValid()) { + return NULL; + } + return dockedShuttle.GetEntity(); +} diff --git a/neo/prey/Prey/game_shuttledock.h b/neo/prey/Prey/game_shuttledock.h new file mode 100644 index 00000000..0598a26e --- /dev/null +++ b/neo/prey/Prey/game_shuttledock.h @@ -0,0 +1,67 @@ + +#ifndef __GAME_SHUTTLEDOCK_H__ +#define __GAME_SHUTTLEDOCK_H__ + +class hhShuttleDock : public hhDock { +public: + CLASS_PROTOTYPE( hhShuttleDock ); + + hhShuttleDock(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void Think(); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void ShuttleExit(hhShuttle *shuttle); + virtual bool IsLocked() { return bLocked; } + virtual bool CanExitLocked() { return bCanExitLocked; } + virtual bool AllowsBoost() { return !bLocked; } + virtual bool AllowsFiring() { return false; } + virtual bool AllowsExit(); + virtual bool IsTeleportDest(){ return true; } + virtual void UpdateAxis( const idMat3 &newAxis ) { } + + virtual bool Recharges() const { return true; } + + virtual hhShuttle *GetDockedShuttle(void); + +protected: + void AttachShuttle(hhShuttle *shuttle); + void DetachShuttle(hhShuttle *shuttle); + hhBeamSystem * SpawnDockingBeam(idVec3 &offset); + void SpawnConsole(); + virtual void Lock(); + virtual void Unlock(); + + void Event_SpawnConsole(); + virtual void Event_Remove(); + virtual void Event_PostSpawn(); + +protected: + idVec3 offsetNozzle; + idVec3 offsetConsole; + idVec3 offsetShuttlePoint; + idEntityPtr dockedShuttle; + idEntityPtr dockingBeam; + hhForce_Converge dockingForce; + int shuttleCount; + int lastConsoleAttempt; + int amountHealth; + int amountPower; + float maxDistance; + bool bLocked; + bool bLockOnEntry; + bool bCanExitLocked; + bool bPlayingRechargeSound; +}; + +#endif diff --git a/neo/prey/Prey/game_shuttletransport.cpp b/neo/prey/Prey/game_shuttletransport.cpp new file mode 100644 index 00000000..3f5ee8b8 --- /dev/null +++ b/neo/prey/Prey/game_shuttletransport.cpp @@ -0,0 +1,244 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//----------------------------------------------------------------------- +// +// hhShuttleTransport +// +//----------------------------------------------------------------------- + +const idEventDef EV_FadeOutTransporter("fadeOutTransporter"); + +CLASS_DECLARATION(hhDock, hhShuttleTransport) + EVENT( EV_DockLock, hhShuttleTransport::Event_Lock) + EVENT( EV_DockUnlock, hhShuttleTransport::Event_Unlock) + EVENT( EV_Activate, hhShuttleTransport::Event_Activate) + EVENT( EV_FadeOutTransporter, hhShuttleTransport::Event_FadeOut) + EVENT( EV_Remove, hhShuttleTransport::Event_Remove) +END_CLASS + +void hhShuttleTransport::Spawn() { + dockingBeam = NULL; + dockedShuttle = NULL; + shuttleCount = 0; + bLocked = false; + + amountHealth = spawnArgs.GetInt("amounthealth"); + amountPower = spawnArgs.GetInt("amountpower"); + + bCanExitLocked = spawnArgs.GetBool("canExitLocked"); + bLockOnEntry = spawnArgs.GetBool("lockOnEntry"); + bAllowFiring = spawnArgs.GetBool("allowFiring"); + offsetNozzle = spawnArgs.GetVector("offset_nozzle1"); +// offsetNozzle2 = spawnArgs.GetVector("offset_nozzle2"); + offsetShuttlePoint = spawnArgs.GetVector("offset_shuttlepoint"); + +// dockingBeam = SpawnDockingBeam(offsetNozzle); + + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); + dockingForce.SetTarget(GetOrigin() + offsetShuttlePoint * GetAxis()); + + // Fade in + SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); // Growth start time + SetShaderParm(5, 1.0f); // Growth direction (in) + SetShaderParm(6, 1.0f); // Make Beam opaque + StartSound("snd_fadein", SND_CHANNEL_ANY); +} + +void hhShuttleTransport::Save(idSaveGame *savefile) const { + savefile->WriteVec3( offsetNozzle ); + savefile->WriteVec3( offsetShuttlePoint ); + savefile->WriteObject( dockedShuttle ); + savefile->WriteObject( dockingBeam ); + savefile->WriteStaticObject( dockingForce ); + savefile->WriteInt( shuttleCount ); + savefile->WriteInt( amountHealth ); + savefile->WriteInt( amountPower ); + savefile->WriteBool( bLocked ); + savefile->WriteBool( bLockOnEntry ); + savefile->WriteBool( bCanExitLocked ); +} + +void hhShuttleTransport::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( offsetNozzle ); + savefile->ReadVec3( offsetShuttlePoint ); + savefile->ReadObject( reinterpret_cast(dockedShuttle) ); + savefile->ReadObject( reinterpret_cast(dockingBeam) ); + savefile->ReadStaticObject( dockingForce ); + savefile->ReadInt( shuttleCount ); + savefile->ReadInt( amountHealth ); + savefile->ReadInt( amountPower ); + savefile->ReadBool( bLocked ); + savefile->ReadBool( bLockOnEntry ); + savefile->ReadBool( bCanExitLocked ); +} + +hhBeamSystem *hhShuttleTransport::SpawnDockingBeam(idVec3 &offset) { + idVec3 pos = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis(); + hhBeamSystem *beam = hhBeamSystem::SpawnBeam(pos, spawnArgs.GetString("beam_docking")); + assert(beam); + beam->SetOrigin(pos); + beam->SetAxis(GetPhysics()->GetAxis()); + beam->Bind(this, false); + beam->Hide(); + return beam; +} + +bool hhShuttleTransport::ValidEntity(idEntity *ent) { + return (ent->IsType(hhShuttle::Type) && !ent->IsHidden()); +} + +void hhShuttleTransport::EntityEntered(idEntity *ent) { + if (ent->IsType(hhShuttle::Type)) { + shuttleCount++; + hhShuttle *shuttle = static_cast(ent); + if (shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + } +} + +void hhShuttleTransport::EntityEncroaching(idEntity *ent) { + + if (ent->IsType(hhShuttle::Type)) { + hhShuttle *shuttle = static_cast(ent); + + // Check for any shuttles that just became valid + if (dockedShuttle == NULL && shuttle->IsVehicle()) { + AttachShuttle(shuttle); + } + + // Recharge docked shuttles + if (shuttle == dockedShuttle) { + + //HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow + if(USERCMD_HZ == 30) { + shuttle->GiveHealth(2*amountHealth); + shuttle->GivePower(2*amountPower); + } else { + shuttle->GiveHealth(amountHealth); + shuttle->GivePower(amountPower); + } + } + } +} + +void hhShuttleTransport::EntityLeaving(idEntity *ent) { + if (ent->IsType(hhShuttle::Type)) { + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); + if (ent == dockedShuttle && !IsLocked()) { + DetachShuttle(dockedShuttle); + } + } +} + +void hhShuttleTransport::AttachShuttle(hhShuttle *shuttle) { + if (dockedShuttle == NULL) { + dockedShuttle = shuttle; + dockingForce.SetEntity(dockedShuttle); + if (dockingBeam) { + dockingBeam->SetTargetEntity(dockedShuttle, 0, dockedShuttle->spawnArgs.GetVector("offset_dockingpoint")); + dockingBeam->Activate( true ); + } + + dockedShuttle->SetDock(this); + dockedShuttle->InitiateRecharging(); + StartSound("snd_getin", SND_CHANNEL_ANY); + StartSound("snd_looper", SND_CHANNEL_DOCKED); + + if (bLockOnEntry) { + Lock(); + } + ActivateTargets( shuttle ); // Fire triggers + BecomeActive(TH_THINK); + SetShaderParm(6, 0.0f); // Make beam transparent + } +} + +void hhShuttleTransport::DetachShuttle(hhShuttle *shuttle) { + assert (shuttle==dockedShuttle); + if (dockedShuttle != NULL) { + dockingForce.SetEntity(NULL); + if (dockingBeam) { + dockingBeam->SetTargetEntity(NULL); + dockingBeam->Activate( false ); + } + + dockedShuttle->SetDock(NULL); + dockedShuttle->FinishRecharging(); + dockedShuttle = NULL; + StartSound("snd_getout", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_DOCKED); + Unlock(); // Shouldn't get in here when locked except when a shuttle dies within + BecomeInactive(TH_THINK); // Keep thinking so the restore position is updated + SetShaderParm(6, 1.0f); // Make beam opaque + } +} + +void hhShuttleTransport::Think() { + hhDock::Think(); + if (thinkFlags & TH_THINK) { + // Apply docking force to shuttle if docked, even if not in zone + assert(dockingForce.GetEntity() == dockedShuttle); + dockingForce.SetTarget(GetOrigin() + offsetShuttlePoint * GetAxis()); + dockingForce.Evaluate(gameLocal.time); + } +} + +void hhShuttleTransport::ShuttleExit(hhShuttle *shuttle) { + DetachShuttle(shuttle); + + // NOTE: Since zones don't get exit messages for entities that are destroyed, this is needed to + // decrease the shuttle count manually. + // However, since the shuttle physics shrinks when released, it's possible to get "nearly" outside + // the zone before releasing, in which case the exit DOES get called, screwing up the count. + // So... we bound the count to zero. + shuttleCount--; + shuttleCount = idMath::ClampInt(0, shuttleCount, shuttleCount); +} + +void hhShuttleTransport::UpdateAxis( const idMat3 &newAxis ) { + // Yaw transport to match yaw of shuttle + idAngles ang = newAxis.ToAngles(); + ang.pitch = ang.roll = 0.0f; + SetAxis(ang.ToMat3()); +} + +void hhShuttleTransport::Lock() { + bLocked = true; + dockedShuttle->SetOrigin(GetOrigin() + offsetShuttlePoint * GetAxis()); + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce_locked")); + dockedShuttle->Bind(this, false); +} + +void hhShuttleTransport::Unlock() { + bLocked = false; + dockingForce.SetRestoreFactor(spawnArgs.GetFloat("dockingforce")); + if (dockedShuttle) { + dockedShuttle->Unbind(); + } +} + +void hhShuttleTransport::Event_FadeOut() { + // Fade out + SetShaderParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); // Growth start time + SetShaderParm(5, -1.0f); // Growth direction (out) + SetShaderParm(6, 0.0f); // Make Beam translucent + StartSound("snd_fadeout", SND_CHANNEL_ANY); + + PostEventMS(&EV_Remove, 1000); +} + +void hhShuttleTransport::Event_Remove() { + if (IsLocked()) { + Unlock(); + } + if (dockedShuttle) { + DetachShuttle(dockedShuttle); + } + hhDock::Event_Remove(); +} diff --git a/neo/prey/Prey/game_shuttletransport.h b/neo/prey/Prey/game_shuttletransport.h new file mode 100644 index 00000000..352ba8f0 --- /dev/null +++ b/neo/prey/Prey/game_shuttletransport.h @@ -0,0 +1,51 @@ + +#ifndef __GAME_SHUTTLETRANSPORT_H__ +#define __GAME_SHUTTLETRANSPORT_H__ + +class hhShuttleTransport : public hhDock { +public: + CLASS_PROTOTYPE( hhShuttleTransport ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void ShuttleExit(hhShuttle *shuttle); + virtual bool IsLocked() { return bLocked; } + virtual bool CanExitLocked() { return bCanExitLocked; } + virtual bool AllowsBoost() { return false; } + virtual bool AllowsFiring() { return bAllowFiring; } + virtual bool AllowsExit() { return false; } + virtual bool IsTeleportDest(){ return false; } + virtual void UpdateAxis( const idMat3 &newAxis ); + +protected: + void AttachShuttle(hhShuttle *shuttle); + void DetachShuttle(hhShuttle *shuttle); + hhBeamSystem * SpawnDockingBeam(idVec3 &offset); + virtual void Lock(); + virtual void Unlock(); + void Event_FadeOut(); + virtual void Event_Remove(); + +protected: + idVec3 offsetNozzle; + idVec3 offsetShuttlePoint; + hhShuttle * dockedShuttle; + hhBeamSystem * dockingBeam; + hhForce_Converge dockingForce; + int shuttleCount; + int amountHealth; + int amountPower; + bool bLocked; + bool bLockOnEntry; + bool bCanExitLocked; + bool bAllowFiring; +}; + +#endif diff --git a/neo/prey/Prey/game_skybox.cpp b/neo/prey/Prey/game_skybox.cpp new file mode 100644 index 00000000..2303bbb5 --- /dev/null +++ b/neo/prey/Prey/game_skybox.cpp @@ -0,0 +1,91 @@ +//************************************************************************** +//** +//** GAME_SKYBOX.CPP +//** +//** Game code for Prey-specific skyboxes +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +CLASS_DECLARATION(idEntity, hhSkybox) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSkybox::Spawn +// +//========================================================================== + +void hhSkybox::Spawn(void) { + // Setup the initial state for the skybox + SetSkin( NULL ); + + BecomeActive( TH_UPDATEVISUALS ); + + UpdateVisuals(); +} + +/* +================ +hhSkybox::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void hhSkybox::Present( void ) { + PROFILE_SCOPE("Present", PROFMASK_NORMAL); + + if ( !gameLocal.isNewFrame ) { + 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 + // HUMANHEAD tmj: this is the only change from idEntity::Present. Don't care if the + // skybox is in the PVS since we only build the remoteRenderView on the first think + // before the skybox goes inactive. + 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/neo/prey/Prey/game_skybox.h b/neo/prey/Prey/game_skybox.h new file mode 100644 index 00000000..41aeb745 --- /dev/null +++ b/neo/prey/Prey/game_skybox.h @@ -0,0 +1,14 @@ + +#ifndef __GAME_SKYBOX_H__ +#define __GAME_SKYBOX_H__ + +class hhSkybox : public idEntity { +public: + CLASS_PROTOTYPE( hhSkybox ); + + void Spawn(void); + + virtual void Present( void ); +}; + +#endif /* __GAME_SKYBOX_H__ */ diff --git a/neo/prey/Prey/game_slots.cpp b/neo/prey/Prey/game_slots.cpp new file mode 100644 index 00000000..160b1f0b --- /dev/null +++ b/neo/prey/Prey/game_slots.cpp @@ -0,0 +1,417 @@ +// hhSlots +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +enum { + SLOTRESULT_NONE=0, + SLOTRESULT_LOSE=1, + SLOTRESULT_WIN=2 +}; + +const idEventDef EV_Spin("spin", NULL); + +CLASS_DECLARATION(hhConsole, hhSlots) + EVENT( EV_Spin, hhSlots::Event_Spin) +END_CLASS + + +void hhSlots::Spawn() { + int ix; + + fruitTextures[FRUIT_CHERRY] = spawnArgs.GetString("mtr_cherry"); + fruitTextures[FRUIT_ORANGE] = spawnArgs.GetString("mtr_orange"); + fruitTextures[FRUIT_LEMON] = spawnArgs.GetString("mtr_lemon"); + fruitTextures[FRUIT_APPLE] = spawnArgs.GetString("mtr_apple"); + fruitTextures[FRUIT_GRAPE] = spawnArgs.GetString("mtr_grape"); + fruitTextures[FRUIT_MELON] = spawnArgs.GetString("mtr_melon"); + fruitTextures[FRUIT_BAR] = spawnArgs.GetString("mtr_bar"); + fruitTextures[FRUIT_BARBAR] = spawnArgs.GetString("mtr_barbar"); + fruitTextures[FRUIT_BARBARBAR] = spawnArgs.GetString("mtr_barbarbar"); + + for (ix=0; ixWriteInt( Bet ); + savefile->WriteInt( PlayerBet ); + savefile->WriteInt( PlayerCredits ); + savefile->WriteInt( result ); + savefile->WriteInt( creditsWon ); + savefile->WriteInt( victoryAmount ); + for (i=0; iWriteString( fruitTextures[i] ); + } + + for (i=0; iWriteInt( reel1[i] ); + savefile->WriteInt( reel2[i] ); + savefile->WriteInt( reel3[i] ); + } + + savefile->WriteFloat( reelPos1 ); + savefile->WriteFloat( reelPos2 ); + savefile->WriteFloat( reelPos3 ); + + savefile->WriteFloat( reelRate1 ); + savefile->WriteFloat( reelRate2 ); + savefile->WriteFloat( reelRate3 ); + savefile->WriteBool( bSpinning ); + savefile->WriteBool( bCanSpin ); + savefile->WriteBool( bCanIncBet ); + savefile->WriteBool( bCanDecBet ); +} + +void hhSlots::Restore( idRestoreGame *savefile ) { + int i; + savefile->ReadInt( Bet ); + savefile->ReadInt( PlayerBet ); + savefile->ReadInt( PlayerCredits ); + savefile->ReadInt( result ); + savefile->ReadInt( creditsWon ); + savefile->ReadInt( victoryAmount ); + for (i=0; iReadString( fruitTextures[i] ); + } + + for (i=0; iReadInt( (int&)reel1[i] ); + savefile->ReadInt( (int&)reel2[i] ); + savefile->ReadInt( (int&)reel3[i] ); + } + + savefile->ReadFloat( reelPos1 ); + savefile->ReadFloat( reelPos2 ); + savefile->ReadFloat( reelPos3 ); + + savefile->ReadFloat( reelRate1 ); + savefile->ReadFloat( reelRate2 ); + savefile->ReadFloat( reelRate3 ); + savefile->ReadBool( bSpinning ); + savefile->ReadBool( bCanSpin ); + savefile->ReadBool( bCanIncBet ); + savefile->ReadBool( bCanDecBet ); +} + +void hhSlots::Spin() { + bSpinning = true; + Bet = PlayerBet; + bCanSpin = bCanIncBet = bCanDecBet = false; + reelRate1 = 2000+gameLocal.random.RandomFloat()*100; + reelRate2 = 3000+gameLocal.random.RandomFloat()*100; + reelRate3 = 4000+gameLocal.random.RandomFloat()*100; + result = SLOTRESULT_NONE; + creditsWon = 0; +} + +void hhSlots::IncBet() { + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanIncBet) { + int oldBet = PlayerBet; + PlayerBet = idMath::ClampInt(PlayerBet, PlayerCredits, PlayerBet+amount); + PlayerBet = idMath::ClampInt(0, 999999, PlayerBet); + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + +void hhSlots::DecBet() { + int amount = 1; + idUserInterface *gui = renderEntity.gui[0]; + if (gui) { + amount = gui->GetStateInt("increment"); + } + + if (bCanDecBet) { + int oldBet = PlayerBet; + if (PlayerBet > amount) { + PlayerBet -= amount; + } + else if (PlayerBet > 1) { + PlayerBet = 1; + } + if (PlayerBet != oldBet) { + StartSound( "snd_betchange", SND_CHANNEL_ANY ); + } + } + UpdateView(); +} + +int MaskForFruit(fruit_t fruit) { + int mask = -1; + switch(fruit) { + case FRUIT_CHERRY: mask = MASK_CHERRY; break; + case FRUIT_ORANGE: mask = MASK_ORANGE; break; + case FRUIT_LEMON: mask = MASK_LEMON; break; + case FRUIT_APPLE: mask = MASK_APPLE; break; + case FRUIT_GRAPE: mask = MASK_GRAPE; break; + case FRUIT_MELON: mask = MASK_MELON; break; + case FRUIT_BAR: mask = MASK_BAR; break; + case FRUIT_BARBAR: mask = MASK_BARBAR; break; + case FRUIT_BARBARBAR: mask = MASK_BARBARBAR; break; + } + assert(mask != -1); + return mask; +} + +int ReelPos2Slot(int rpos) { + return ((rpos + REEL_LENGTH) % REEL_LENGTH) / SLOT_HEIGHT; +} + +int ReelPos2SlotPos(int rpos) { + return ((rpos + REEL_LENGTH) % REEL_LENGTH) % SLOT_HEIGHT; +} + + +void hhSlots::UpdateView() { + bool bGameOver = false; + idUserInterface *gui = renderEntity.gui[0]; + + if (gui) { + if (PlayerCredits <= 0) { + bCanIncBet = bCanDecBet = bCanSpin = false; + bGameOver = true; + } + + gui->SetStateBool("bgameover", bGameOver); + gui->SetStateBool("bcanincbet", bCanIncBet); + gui->SetStateBool("bcandecbet", bCanDecBet); + gui->SetStateBool("bcanspin", bCanSpin); + gui->SetStateInt("currentbet", PlayerBet); + gui->SetStateInt("credits", PlayerCredits); + gui->SetStateInt("result", result); + gui->SetStateInt("creditswon", creditsWon); + + gui->SetStateInt("reel1pos", (int)reelPos1); + gui->SetStateInt("reel2pos", (int)reelPos2); + gui->SetStateInt("reel3pos", (int)reelPos3); + + gui->SetStateInt("reel1rate", (int)reelRate1); + gui->SetStateInt("reel2rate", (int)reelRate2); + gui->SetStateInt("reel3rate", (int)reelRate3); + + // Clear + int fruit1, fruit2, fruit3; + + // Reel 1 + fruit1 = reel1[ ReelPos2Slot( reelPos1-SLOT_HEIGHT )]; + fruit2 = reel1[ ReelPos2Slot( reelPos1 )]; + fruit3 = reel1[ ReelPos2Slot( reelPos1+SLOT_HEIGHT )]; + gui->SetStateString("r1s1_texture", fruitTextures[fruit1]); + gui->SetStateString("r1s2_texture", fruitTextures[fruit2]); + gui->SetStateString("r1s3_texture", fruitTextures[fruit3]); + + // Reel 2 + fruit1 = reel2[ ReelPos2Slot( reelPos2-SLOT_HEIGHT )]; + fruit2 = reel2[ ReelPos2Slot( reelPos2 )]; + fruit3 = reel2[ ReelPos2Slot( reelPos2+SLOT_HEIGHT )]; + gui->SetStateString("r2s1_texture", fruitTextures[fruit1]); + gui->SetStateString("r2s2_texture", fruitTextures[fruit2]); + gui->SetStateString("r2s3_texture", fruitTextures[fruit3]); + + // Reel 3 + fruit1 = reel3[ ReelPos2Slot( reelPos3-SLOT_HEIGHT )]; + fruit2 = reel3[ ReelPos2Slot( reelPos3 )]; + fruit3 = reel3[ ReelPos2Slot( reelPos3+SLOT_HEIGHT )]; + gui->SetStateString("r3s1_texture", fruitTextures[fruit1]); + gui->SetStateString("r3s2_texture", fruitTextures[fruit2]); + gui->SetStateString("r3s3_texture", fruitTextures[fruit3]); + + gui->StateChanged(gameLocal.time, true); + CallNamedEvent("Update"); + } +} + +void hhSlots::CheckVictory() { + + #define NUM_VICTORIES 16 + static victory_t victoryTable[NUM_VICTORIES] = { + { MASK_BARBARBAR, MASK_BARBARBAR, MASK_BARBARBAR, 10000}, + { MASK_BARBAR, MASK_BARBAR, MASK_BARBAR, 1000}, + { MASK_BAR, MASK_BAR, MASK_BAR, 500}, + { MASK_ANYBAR, MASK_ANYBAR, MASK_ANYBAR, 100}, + + { MASK_MELON, MASK_MELON, MASK_MELON, 60}, + { MASK_GRAPE, MASK_GRAPE, MASK_GRAPE, 50}, + { MASK_APPLE, MASK_APPLE, MASK_APPLE, 40}, + { MASK_LEMON, MASK_LEMON, MASK_LEMON, 30}, + { MASK_ORANGE, MASK_ORANGE, MASK_ORANGE, 20}, + { MASK_CHERRY, MASK_CHERRY, MASK_CHERRY, 10}, + + { MASK_CHERRY, MASK_CHERRY, MASK_ANY, 5}, + { MASK_ANY, MASK_CHERRY, MASK_CHERRY, 5}, + { MASK_CHERRY, MASK_ANY, MASK_CHERRY, 5}, + + { MASK_CHERRY, MASK_ANY, MASK_ANY, 2}, + { MASK_ANY, MASK_CHERRY, MASK_ANY, 2}, + { MASK_ANY, MASK_ANY, MASK_CHERRY, 2} + }; + + PlayerCredits -= Bet; + result = SLOTRESULT_LOSE; + creditsWon = 0; + for (int ix=0; ix= victoryAmount) { + StartSound( "snd_victory", SND_CHANNEL_ANY ); + ActivateTargets( gameLocal.GetLocalPlayer() ); + victoryAmount = 0; + } + else if (victoryTable[ix].payoff > 5) { + StartSound( "snd_winbig", SND_CHANNEL_ANY ); + } + else { + StartSound( "snd_win", SND_CHANNEL_ANY ); + } + break; + } + } + + PlayerBet = idMath::ClampInt(0, PlayerCredits, PlayerBet); +} + + +bool hhSlots::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + + if (token == ";") { + return false; + } + + if (token.Icmp("spin") == 0) { + BecomeActive(TH_MISC3); + StartSound( "snd_spin", SND_CHANNEL_ANY ); + Spin(); + } + else if (token.Icmp("incbet") == 0) { + IncBet(); + } + else if (token.Icmp("decbet") == 0) { + DecBet(); + } + else if (token.Icmp("reset") == 0) { + Reset(); + } + else if (token.Icmp("restart") == 0) { + bCanSpin = 1; + bCanIncBet = 1; + bCanDecBet = 1; + PlayerCredits = spawnArgs.GetInt("credits"); + Bet = PlayerBet = 1; + UpdateView(); + } + else { + src->UnreadToken(&token); + return false; + } + + return true; +} + +void hhSlots::Think() { + hhConsole::Think(); + + if (thinkFlags & TH_MISC3) { + if (bSpinning) { + + float deltaTime = MS2SEC(gameLocal.msec); + if (reelRate1 > 0.0f) { + reelPos1 = ((int)(reelPos1 - reelRate1 * deltaTime) + REEL_LENGTH) % REEL_LENGTH; + reelRate1 *= 0.98f; + if (reelRate1 < MINIMUM_REEL_RATE) { + reelRate1 = 0.0f; + StartSound( "snd_stop", SND_CHANNEL_ANY ); + } + } + if (reelRate2 > 0.0f) { + reelPos2 = ((int)(reelPos2 - reelRate2 * deltaTime) + REEL_LENGTH) % REEL_LENGTH; + reelRate2 *= 0.98f; + if (reelRate2 < MINIMUM_REEL_RATE) { + reelRate2 = 0.0f; + StartSound( "snd_stop", SND_CHANNEL_ANY ); + } + } + if (reelRate3 > 0.0f) { + reelPos3 = ((int)(reelPos3 - reelRate3 * deltaTime) + REEL_LENGTH) % REEL_LENGTH; + reelRate3 *= 0.98f; + if (reelRate3 < MINIMUM_REEL_RATE) { + reelRate3 = 0.0f; + StartSound( "snd_stop", SND_CHANNEL_ANY ); + } + } + + if (reelRate1==0.0f && reelRate2==0.0f && reelRate3==0.0f) { + CheckVictory(); + bSpinning = false; + bCanSpin = bCanIncBet = bCanDecBet = true; + BecomeInactive(TH_MISC3); + } + + UpdateView(); + } + } +} + +void hhSlots::Event_Spin() { + Spin(); +} \ No newline at end of file diff --git a/neo/prey/Prey/game_slots.h b/neo/prey/Prey/game_slots.h new file mode 100644 index 00000000..44b57bba --- /dev/null +++ b/neo/prey/Prey/game_slots.h @@ -0,0 +1,86 @@ + +#ifndef __GAME_SLOTS_H__ +#define __GAME_SLOTS_H__ + + #define SLOT_HEIGHT 100 + #define SLOTS_IN_REEL 10 + #define MINIMUM_REEL_RATE (SLOT_HEIGHT/2) + #define REEL_LENGTH (SLOTS_IN_REEL*SLOT_HEIGHT) + + #define MASK_CHERRY 0x00000001 + #define MASK_ORANGE 0x00000002 + #define MASK_LEMON 0x00000004 + #define MASK_APPLE 0x00000008 + #define MASK_GRAPE 0x00000010 + #define MASK_MELON 0x00000020 + #define MASK_BAR 0x00000100 + #define MASK_BARBAR 0x00000200 + #define MASK_BARBARBAR 0x00000400 + + #define MASK_ANYBAR 0x00000700 + #define MASK_ANY 0xffffffff + + typedef enum { + FRUIT_CHERRY=0, + FRUIT_ORANGE, + FRUIT_LEMON, + FRUIT_APPLE, + FRUIT_GRAPE, + FRUIT_MELON, + FRUIT_BAR, + FRUIT_BARBAR, + FRUIT_BARBARBAR, + NUM_FRUITS + }fruit_t; + + typedef struct victory_s { + int f1, f2, f3; + int payoff; + }victory_t; + + +class hhSlots : public hhConsole { +public: + CLASS_PROTOTYPE( hhSlots ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spin(); + void IncBet(); + void DecBet(); + void CheckVictory(); + + void Reset(); + void UpdateView(); + bool HandleSingleGuiCommand(idEntity *entityGui, idLexer *src); + +protected: + virtual void Think( void ); + void Event_Spin(); + +private: + int Bet; + int PlayerBet; + int PlayerCredits; + int result; + int creditsWon; + int victoryAmount; + + idStr fruitTextures[NUM_FRUITS]; + + fruit_t reel1[SLOTS_IN_REEL]; + fruit_t reel2[SLOTS_IN_REEL]; + fruit_t reel3[SLOTS_IN_REEL]; + float reelPos1, reelPos2, reelPos3; + float reelRate1, reelRate2, reelRate3; + bool bSpinning; + + bool bCanSpin; + bool bCanIncBet; + bool bCanDecBet; +}; + + +#endif /* __GAME_SLOTS_H__ */ diff --git a/neo/prey/Prey/game_sphere.cpp b/neo/prey/Prey/game_sphere.cpp new file mode 100644 index 00000000..fb4fea19 --- /dev/null +++ b/neo/prey/Prey/game_sphere.cpp @@ -0,0 +1,151 @@ +// Game_Sphere.cpp +// +// pushable exploding sphere + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + + +CLASS_DECLARATION(hhMoveable, hhSphere) + EVENT( EV_Touch, hhSphere::Event_Touch ) +END_CLASS + + +hhSphere::hhSphere() { + additionalAxis.Identity(); +} + +void hhSphere::Spawn(void) { + + radius = (GetPhysics()->GetBounds()[1][0] - GetPhysics()->GetBounds()[0][0]) * 0.5f; + lastOrigin = GetPhysics()->GetOrigin(); + additionalAxis.Identity(); + CreateLight(); + + fl.takedamage = false; + BecomeActive(TH_THINK); +} + +void hhSphere::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( additionalAxis ); + light.Save(savefile); +} + +void hhSphere::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( additionalAxis ); + light.Restore(savefile); +} + +void hhSphere::CreateLight() { + if ( spawnArgs.GetBool("haslight") ) { + idStr light_shader = spawnArgs.GetString("mtr_light"); + idVec3 light_color = spawnArgs.GetVector("light_color"); + idVec3 light_frustum = spawnArgs.GetVector("light_frustum"); + idVec3 light_offset = spawnArgs.GetVector("offset_light"); + idVec3 light_target = spawnArgs.GetVector("offset_lighttarget"); + + idDict args; + idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); + light_target.Normalize(); + idMat3 lightAxis = (light_target * GetPhysics()->GetAxis()).hhToMat3(); + + if ( light_shader.Length() ) { + args.Set( "texture", light_shader ); + } + args.SetVector( "origin", lightOrigin ); + args.Set ("angles", lightAxis.ToAngles().ToString()); + args.SetVector( "_color", light_color ); + args.SetVector( "light_target", lightAxis[0] * light_frustum.x ); + args.SetVector( "light_right", lightAxis[1] * light_frustum.y ); + args.SetVector( "light_up", lightAxis[2] * light_frustum.z ); + light = ( idLight * )gameLocal.SpawnEntityType( idLight::Type, &args ); + light->Bind(this, true); + light->SetLightParm( 6, 1.0f ); + light->SetLightParm( SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time) ); + } +} + +void hhSphere::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + // skip idMoveable::Damage which handles damage differently + idEntity::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + + +void hhSphere::RollThink( void ) { + float movedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + + bool wasAtRest = IsAtRest(); + + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + curOrigin = GetPhysics()->GetOrigin(); + + dir = curOrigin - lastOrigin; + float movedDistanceSquared = dir.LengthSqr(); + + // if the sphere moved + if ( movedDistanceSquared > 0.0f && movedDistanceSquared < 20.0f) { + + gravityNormal = GetPhysics()->GetGravityNormal(); + + // movement since last frame + movedDistance = idMath::Sqrt( movedDistanceSquared ); + dir *= 1.0f / movedDistance; + + // Get local coordinate axes + idVec3 right = -dir.Cross(gravityNormal); + + // Rotate about it proportional to the distance moved using axis/angle + angle = 180.0f * movedDistance / (radius*idMath::PI); + additionalAxis *= (idRotation( vec3_origin, right, angle).ToMat3()); + } + + // save state for next think + lastOrigin = curOrigin; + } + + Present(); +} + +void hhSphere::Think() { + RollThink(); +} + +void hhSphere::ClientPredictionThink( void ) { + RollThink(); +} + +bool hhSphere::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis * GetPhysics()->GetAxis().Inverse(); + return true; +} + +void hhSphere::Event_Touch( idEntity *other, trace_t *trace ) { + if (spawnArgs.GetBool("walkthrough")) { + idVec3 otherVel = other->GetPhysics()->GetLinearVelocity(); + float otherSpeed = otherVel.NormalizeFast(); + if (otherSpeed > 50.0f) { // && GetPhysics()->IsAtRest()) { + idVec3 toSide = hhUtils::RandomSign() * other->GetAxis()[1]; + idVec3 toMoveable = GetOrigin() - other->GetOrigin(); + toMoveable.NormalizeFast(); + idVec3 newVel = ( 3*otherVel + toSide + hhUtils::RandomVector() ) * (1.0f/5.0f); + newVel.z = 0.0f; + newVel.NormalizeFast(); + newVel *= otherSpeed*1.5f; + GetPhysics()->SetLinearVelocity(newVel); + } + } +} diff --git a/neo/prey/Prey/game_sphere.h b/neo/prey/Prey/game_sphere.h new file mode 100644 index 00000000..e7b1d455 --- /dev/null +++ b/neo/prey/Prey/game_sphere.h @@ -0,0 +1,31 @@ + +#ifndef __GAME_SPHERE_H__ +#define __GAME_SPHERE_H__ + +class hhSphere : public hhMoveable { +public: + CLASS_PROTOTYPE( hhSphere ); + + hhSphere(); + void Spawn( void ); + 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 bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + +protected: + void RollThink( void ); + void CreateLight(); + void Event_Touch( idEntity *other, trace_t *trace ); + +private: + float radius; // radius of sphere + idVec3 lastOrigin; // origin last frame + idMat3 additionalAxis; // transformation for visual model + idEntityPtr light; // light +}; + + +#endif /* __GAME_SPHERE_H__ */ diff --git a/neo/prey/Prey/game_spherepart.cpp b/neo/prey/Prey/game_spherepart.cpp new file mode 100644 index 00000000..263a1c78 --- /dev/null +++ b/neo/prey/Prey/game_spherepart.cpp @@ -0,0 +1,319 @@ +//************************************************************************** +//** +//** GAME_SPHEREPART.CPP +//** +//** SphereParts are generic environment objects that animate and can be +//** interacted with in simple general ways: Touched, shot / killed. +//** +//** Pulse Tubes should be able to: +//** - Animate +//** - Randomly play other anims +//** - Take Pain +//** - Die (?) +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +const idEventDef EV_Pulse("pulse", NULL); +const idEventDef EV_PlayIdle("", NULL); + +CLASS_DECLARATION( hhAnimatedEntity, hhSpherePart ) + EVENT( EV_Pulse, hhSpherePart::Event_Pulse ) + EVENT( EV_Activate, hhSpherePart::Event_Trigger ) + EVENT( EV_PlayIdle, hhSpherePart::Event_PlayIdle ) +END_CLASS + +// STATE DECLARATIONS ------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSpherePart::Spawn +// +//========================================================================== + +void hhSpherePart::Spawn(void) { + GetPhysics()->SetContents( CONTENTS_BODY ); + + fl.takedamage = true; // Allow the spherepart to be damaged + + spawnArgs.GetFloat("pulsetime", "10", pulseTime); // A pulsetime of zero will ensure that the object never pulses + spawnArgs.GetFloat("pulserandom", "5", pulseRandom); + + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + pulseAnim = GetAnimator()->GetAnim("pulse"); + triggerAnim = GetAnimator()->GetAnim("trigger"); + + BecomeActive(TH_THINK); + + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 100); + StartSound( "snd_idle", SND_CHANNEL_ANY ); + + if ( pulseAnim && pulseTime ) { + PostEventSec(&EV_Pulse, pulseTime + gameLocal.random.RandomFloat() * pulseRandom); + } +} + +void hhSpherePart::Save(idSaveGame *savefile) const { + savefile->WriteFloat( pulseTime ); + savefile->WriteFloat( pulseRandom ); + savefile->WriteInt( idleAnim ); + savefile->WriteInt( painAnim ); + savefile->WriteInt( pulseAnim ); + savefile->WriteInt( triggerAnim ); +} + +void hhSpherePart::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( pulseTime ); + savefile->ReadFloat( pulseRandom ); + savefile->ReadInt( idleAnim ); + savefile->ReadInt( painAnim ); + savefile->ReadInt( pulseAnim ); + savefile->ReadInt( triggerAnim ); +} + +//========================================================================== +// +// hhSpherePart::~hhSpherePart +// +//========================================================================== +hhSpherePart::~hhSpherePart() { +} + +//========================================================================== +// +// hhSpherePart::Damage +// +//========================================================================== + +void hhSpherePart::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + int startTime; + + + if ( painAnim && !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( painAnim ) ) ) { + StartSound( "snd_pain", SND_CHANNEL_ANY ); + GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, painAnim, gameLocal.time, 100); + + startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + } +} + +//========================================================================== +// +// hhSpherePart::Event_Trigger +// +//========================================================================== + +void hhSpherePart::Event_Trigger( idEntity *activator ) { + int startTime; + + if(triggerAnim) { + GetAnimator()->ClearAllAnims( gameLocal.time, 500 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, triggerAnim, gameLocal.time, 500); + + startTime = GetAnimator()->GetAnim( triggerAnim )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + } +} + +//========================================================================== +// +// hhSpherePart::Event_Pulse +// +//========================================================================== + +void hhSpherePart::Event_Pulse(void) { + int startTime; + + if ( pulseAnim && pulseTime ) { + StartSound( "snd_pulse", SND_CHANNEL_ANY ); + + GetAnimator()->ClearAllAnims( gameLocal.time, 500 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, pulseAnim, gameLocal.time, 500); + + startTime = GetAnimator()->GetAnim( pulseAnim )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + + PostEventSec(&EV_Pulse, pulseTime + gameLocal.random.RandomFloat() * pulseRandom); + } +} + +//========================================================================== +// +// hhSpherePart::Event_PlayIdle +// +//========================================================================== + +void hhSpherePart::Event_PlayIdle() { + // We are playing the idle, cancel any pending idles + CancelEvents( &EV_PlayIdle ); + + GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 100); +} + +/********************************************************************** + +hhGenericAnimatedPart + +**********************************************************************/ +CLASS_DECLARATION( hhAnimatedEntity, hhGenericAnimatedPart ) + EVENT( EV_PostSpawn, hhGenericAnimatedPart::Event_PostSpawn ) +END_CLASS + +/* +============ +hhGenericAnimatedPart::Spawn +============ +*/ +void hhGenericAnimatedPart::Spawn() { + if( spawnArgs.GetBool("solid", "1") ) { + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + fl.takedamage = false; + GetPhysics()->SetContents( 0 ); + GetPhysics()->UnlinkClip(); + } + + //rww - make sure it's sent + fl.networkSync = true; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +void hhGenericAnimatedPart::Save(idSaveGame *savefile) const { + owner.Save(savefile); +} + +void hhGenericAnimatedPart::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); +} + +void hhGenericAnimatedPart::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(owner.GetSpawnId(), 32); +} + +void hhGenericAnimatedPart::ReadFromSnapshot( const idBitMsgDelta &msg ) +{ + GetPhysics()->ReadFromSnapshot(msg); + owner.SetSpawnId(msg.ReadBits(32)); +} + +void hhGenericAnimatedPart::ClientPredictionThink( void ) +{ + Think(); +} + + +/* +============ +hhGenericAnimatedPart::SetOwner +============ +*/ +void hhGenericAnimatedPart::SetOwner( idEntity* pOwner ) { + owner = pOwner; +} + +/* +============ +hhGenericAnimatedPart::LinkCombatModel +============ +*/ +void hhGenericAnimatedPart::LinkCombatModel( idEntity* self, const int renderModelHandle ) { + hhAnimatedEntity::LinkCombatModel( (owner.IsValid()) ? owner.GetEntity() : this, renderModelHandle ); +} + +/* +============ +hhGenericAnimatedPart::Damage +============ +*/ +void hhGenericAnimatedPart::Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) { + if( owner.IsValid() && spawnArgs.GetBool("transferDamage") ) { + owner->Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } else { + hhAnimatedEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + +/* +================ +hhGenericAnimatedPart::PlayAnim +================ +*/ +int hhGenericAnimatedPart::PlayAnim( const char* animName, int channel ) { + ClearAllAnims(); + + int anim = GetAnimator()->GetAnim( animName ); + if( !anim ) { + return 0; + } + + GetAnimator()->PlayAnim( channel, anim, gameLocal.GetTime(), 0 ); + return GetAnimator()->CurrentAnim( channel )->GetEndTime() - gameLocal.GetTime(); +} + +/* +================ +hhGenericAnimatedPart::CycleAnim +================ +*/ +void hhGenericAnimatedPart::CycleAnim( const char* animName, int channel ) { + ClearAllAnims(); + + int anim = GetAnimator()->GetAnim( animName ); + if( !anim ) { + return; + } + + GetAnimator()->CycleAnim( channel, anim, gameLocal.GetTime(), 0 ); +} + +/* +================ +hhGenericAnimatedPart::ClearAllAnims +================ +*/ +void hhGenericAnimatedPart::ClearAllAnims() { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), 0 ); +} + +/* +============ +hhGenericAnimatedPart::Event_PostSpawn +============ +*/ +void hhGenericAnimatedPart::Event_PostSpawn() { + idStr ownerName = spawnArgs.GetString( "owner" ); + if( ownerName.Length() ) { + SetOwner( gameLocal.FindEntity(ownerName.c_str()) ); + } +} diff --git a/neo/prey/Prey/game_spherepart.h b/neo/prey/Prey/game_spherepart.h new file mode 100644 index 00000000..15f6b085 --- /dev/null +++ b/neo/prey/Prey/game_spherepart.h @@ -0,0 +1,71 @@ + +#ifndef __GAME_SPHEREPART_H__ +#define __GAME_SPHEREPART_H__ + +extern const idEventDef EV_Pulse; +extern const idEventDef EV_PlayIdle; + +class hhSpherePart : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE(hhSpherePart); + + virtual ~hhSpherePart(); + + void Spawn(void); + 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 ); + +protected: + void Event_Pulse(void); + void Event_Trigger( idEntity *activator ); + void Event_PlayIdle(); + +protected: + float pulseTime; // Time between pulses. Slight randomness is added + float pulseRandom; + + int idleAnim; + int painAnim; + int pulseAnim; + int triggerAnim; +}; + +/********************************************************************** + +hhGenericAnimatedPart + +**********************************************************************/ +class hhGenericAnimatedPart: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhGenericAnimatedPart ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ); + + virtual int PlayAnim( const char* animName, int channel ); + virtual void CycleAnim( const char* animName, int channel ); + virtual void ClearAllAnims(); + + void SetOwner( idEntity* pOwner ); + +protected: + virtual void LinkCombatModel( idEntity* self, const int renderModelHandle ); + +protected: + void Event_PostSpawn(); + +protected: + idEntityPtr owner; +}; + +#endif /* __GAME_SPHEREPART_H__ */ diff --git a/neo/prey/Prey/game_spring.cpp b/neo/prey/Prey/game_spring.cpp new file mode 100644 index 00000000..fd228433 --- /dev/null +++ b/neo/prey/Prey/game_spring.cpp @@ -0,0 +1,191 @@ +/* +game_spring.cpp +*/ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/*********************************************************************** + + hhSpring + +***********************************************************************/ + +const idEventDef EV_HHLinkSpring( "linkspring", "" ); +const idEventDef EV_HHUnlinkSpring( "unlinkspring", "" ); + +CLASS_DECLARATION( idEntity, hhSpring ) + EVENT( EV_HHLinkSpring, hhSpring::Event_LinkSpring ) + EVENT( EV_HHUnlinkSpring, hhSpring::Event_UnlinkSpring ) +END_CLASS + + +/* +================ +hhSpring::Spawn +================ +*/ +void hhSpring::Spawn( void ) { + float Kstretch, damping, restLength, Kcompress; + + spawnArgs.GetString( "ent1", "", name1 ); + spawnArgs.GetString( "ent2", "", name2 ); + 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 ); + spawnArgs.GetFloat( "compress", "0.0f", Kcompress ); + + // HUMANHEAD: Added compression constant + spring.InitSpring( Kstretch, Kcompress, damping, restLength ); + + PostEventMS( &EV_HHLinkSpring, 0 ); +} + +void hhSpring::Save(idSaveGame *savefile) const { + savefile->WriteString( name1 ); + savefile->WriteString( name2 ); + physics1.Save( savefile ); + physics2.Save( savefile ); + savefile->WriteInt( id1 ); + savefile->WriteInt( id2 ); + savefile->WriteVec3( p1 ); + savefile->WriteVec3( p2 ); + savefile->WriteStaticObject( spring ); +} + +void hhSpring::Restore( idRestoreGame *savefile ) { + savefile->ReadString( name1 ); + savefile->ReadString( name2 ); + physics1.Restore( savefile ); + physics2.Restore( savefile ); + savefile->ReadInt( id1 ); + savefile->ReadInt( id2 ); + savefile->ReadVec3( p1 ); + savefile->ReadVec3( p2 ); + savefile->ReadStaticObject( spring ); +} + +/* +================ +hhSpring::Think + HUMANHEAD +================ +*/ +#define TEST_SPRINGS +void hhSpring::Think() { + if (thinkFlags & TH_THINK) { + spring.Evaluate( gameLocal.time ); + +#ifdef TEST_SPRINGS + idVec3 start, end, origin; + idMat3 axis; + + start = p1; + if ( physics1.IsValid() ) { + axis = physics1->GetPhysics()->GetAxis(id1); + origin = physics1->GetPhysics()->GetOrigin(id1); + start = origin + p1 * axis; + } + + end = p2; + if ( physics2.IsValid() ) { + axis = physics2->GetPhysics()->GetAxis(id2); + origin = physics2->GetPhysics()->GetOrigin(id2); + end = origin + p2 * axis; + } + + gameRenderWorld->DebugLine( colorYellow, start, end, 0, true ); +#endif + } +} + +/* +================ +hhSpring::LinkSpring + HUMANHEAD +================ +*/ +void hhSpring::LinkSpring(idEntity *ent1, idEntity *ent2 ) { + LinkSpringAll(ent1, id1, p1, ent2, id2, p2); +} + +/* +================ +hhSpring::LinkSpringIDs + HUMANHEAD +================ +*/ +void hhSpring::LinkSpringIDs(idEntity *ent1, int bodyID1, idEntity *ent2, int bodyID2) { + id1 = bodyID1; + id2 = bodyID2; + LinkSpringAll(ent1, id1, p1, ent2, id2, p2); +} + +/* +================ +hhSpring::LinkSpringAll + HUMANHEAD +================ +*/ +void hhSpring::LinkSpringAll(idEntity *ent1, int bodyID1, idVec3 &offset1, + idEntity *ent2, int bodyID2, idVec3 &offset2) { + + physics1 = ent1; + physics2 = ent2; + spring.SetPosition( physics1.GetEntity(), bodyID1, offset1, physics2.GetEntity(), bodyID2, offset2 ); + BecomeActive(TH_THINK); +} + +/* +================ +hhSpring::UnLinkSpring + HUMANHEAD +================ +*/ +void hhSpring::UnLinkSpring() { + BecomeInactive(TH_THINK); +} + +/* +================ +hhSpring::SpringSettings + HUMANHEAD +================ +*/ +void hhSpring::SpringSettings(float kStretch, float kCompress, float damping, float restLength) { + spring.InitSpring(kStretch, kCompress, damping, restLength); +} + +/* +================ +hhSpring::Event_LinkSpring +================ +*/ +void hhSpring::Event_LinkSpring( void ) { + idEntity *ent1, *ent2; + + ent1 = gameLocal.FindEntity( name1 ); + if ( !ent1 ) { + ent1 = gameLocal.entities[ENTITYNUM_WORLD]; + } + ent2 = gameLocal.FindEntity( name2 ); + if ( !ent2 ) { + ent2 = gameLocal.entities[ENTITYNUM_WORLD]; + } + LinkSpring(ent1, ent2); +} + +/* +================ +hhSpring::Event_UnlinkSpring +================ +*/ +void hhSpring::Event_UnlinkSpring( void ) { + UnLinkSpring(); +} diff --git a/neo/prey/Prey/game_spring.h b/neo/prey/Prey/game_spring.h new file mode 100644 index 00000000..15a74c84 --- /dev/null +++ b/neo/prey/Prey/game_spring.h @@ -0,0 +1,45 @@ + +#ifndef __GAME_SPRING_H__ +#define __GAME_SPRING_H__ + +/*********************************************************************** + +hhSpring + +***********************************************************************/ +extern const idEventDef EV_HHLinkSpring; +extern const idEventDef EV_HHUnlinkSpring; + +class hhSpring : public idEntity { + CLASS_PROTOTYPE( hhSpring ); + +public: + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + // HUMANHEAD pdm + void LinkSpring(idEntity *ent1, idEntity *ent2 ); + void LinkSpringIDs(idEntity *ent1, int bodyID1, idEntity *ent2, int bodyID2); + void LinkSpringAll(idEntity *ent1, int bodyID1, idVec3 &offset1, idEntity *ent2, int bodyID2, idVec3 &offset2); + void UnLinkSpring(); + void SpringSettings(float kStretch, float kCompress, float damping, float restLength); + // HUMANHEAD END + +protected: + void Event_LinkSpring( void ); + void Event_UnlinkSpring( void ); + +protected: + idStr name1, name2; + idEntityPtr physics1, physics2; + int id1, id2; + idVec3 p1, p2; + idForce_Spring spring; +}; + + +#endif /* !__GAME_SPRING_H__ */ diff --git a/neo/prey/Prey/game_sunCorona.cpp b/neo/prey/Prey/game_sunCorona.cpp new file mode 100644 index 00000000..77199612 --- /dev/null +++ b/neo/prey/Prey/game_sunCorona.cpp @@ -0,0 +1,123 @@ +//***************************************************************************** +//** +//** GAME_SUNCORONA.CPP +//** +//** Game code for Sun Coronas +//** +//** TODO: +//** - Implement corona scale (or just leave this for the shader?) +//** - Check if 4096 is good enough for distance -- issues in very large rooms? +//** - Verify if the noFragment check is valid for checking for skyboxes +//** - Make it function corrected with the rifle zoom view +//***************************************************************************** + +// HEADER FILES --------------------------------------------------------------- + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS --------------------------------------------------------------------- + +// TYPES ---------------------------------------------------------------------- + +// CLASS DECLARATIONS --------------------------------------------------------- + +CLASS_DECLARATION( idEntity, hhSunCorona ) +END_CLASS + +// STATE DECLARATIONS --------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES ----------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES ------------------------------------------------ + +// EXTERNAL DATA DECLARATIONS ------------------------------------------------- + +// PUBLIC DATA DEFINITIONS ---------------------------------------------------- + +// PRIVATE DATA DEFINITIONS --------------------------------------------------- + +// CODE ----------------------------------------------------------------------- + +//============================================================================= +// +// hhSunCorona::Spawn +// +//============================================================================= + +void hhSunCorona::Spawn(void) { + corona = declManager->FindMaterial( spawnArgs.GetString( "mtr_corona" ) ); + scale = spawnArgs.GetFloat( "scale", "1" ); + sunVector = spawnArgs.GetVector( "sunVector", "0 0 -1" ); + sunVector.Normalize(); + sunDistance = spawnArgs.GetFloat( "sunDistance", "4096" ); + + GetPhysics()->SetContents(0); + Hide(); + BecomeInactive(TH_THINK); + + gameLocal.SetSunCorona( this ); +} + +void hhSunCorona::Save(idSaveGame *savefile) const { + savefile->WriteMaterial( corona ); + savefile->WriteFloat( scale ); + savefile->WriteVec3( sunVector ); + savefile->WriteFloat( sunDistance ); +} + +void hhSunCorona::Restore( idRestoreGame *savefile ) { + savefile->ReadMaterial( corona ); + savefile->ReadFloat( scale ); + savefile->ReadVec3( sunVector ); + savefile->ReadFloat( sunDistance ); +} + +//============================================================================= +// +// hhSunCorona::~hhSunCorona +// +//============================================================================= + +hhSunCorona::~hhSunCorona() { + corona = NULL; +} + +//============================================================================= +// +// hhhSunCorona::Draw +// +//============================================================================= + +void hhSunCorona::Draw( hhPlayer *player ) { +/* Commented out for E3 demo. Need a better method for visibility, instead of a trace. -cjr + idVec3 v[3]; + float dot[3]; + player->viewAngles.ToVectors( &v[0], &v[1], &v[2] ); + + for( int i = 0; i < 3; i++ ) { + dot[i] = v[i] * sunVector; + } + + if ( dot[0] < 0 ) { + trace_t trace; + idVec3 origin; + idMat3 axis; + player->GetViewPos( origin, axis ); + idVec3 end = origin - sunVector * sunDistance; + gameLocal.clip.TracePoint( trace, origin, end, MASK_SOLID, player ); + + if ( trace.c.material->NoFragment() ) { // Trace succeeded, or it hit a skybox + // Draw a corona on the screen + renderSystem->SetColor4( -dot[0], -dot[0], -dot[0], -dot[0] ); + float hFov = idMath::Sin( g_fov.GetFloat() * 0.5 ); + float vFov = idMath::Sin( g_fov.GetFloat() * 0.5 * 0.75 ); + float sx = ( ( dot[1] - hFov ) / ( -2 * hFov ) ) * 640 - 640; + float sy = ( dot[2] + vFov ) / ( 2 * vFov ) * 480 - 480; + renderSystem->DrawStretchPic( sx, sy, 1280, 960, 0, 0, 1, 1, corona ); + } + } +*/ +} diff --git a/neo/prey/Prey/game_sunCorona.h b/neo/prey/Prey/game_sunCorona.h new file mode 100644 index 00000000..2da20e3c --- /dev/null +++ b/neo/prey/Prey/game_sunCorona.h @@ -0,0 +1,24 @@ + +#ifndef __GAME_SUNCORONA_H__ +#define __GAME_SUNCORONA_H__ + +//----------------------------------------------------------------------------- + +class hhSunCorona : public idEntity { +public: + CLASS_PROTOTYPE(hhSunCorona); + + ~hhSunCorona(); //fixme: does this need to be virtual ? + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Draw( hhPlayer *player ); + +protected: + const idMaterial *corona; + float scale; + idVec3 sunVector; + float sunDistance; +}; + +#endif /* __GAME_SUNCORONA_H__ */ diff --git a/neo/prey/Prey/game_talon.cpp b/neo/prey/Prey/game_talon.cpp new file mode 100644 index 00000000..d7094cbe --- /dev/null +++ b/neo/prey/Prey/game_talon.cpp @@ -0,0 +1,1777 @@ +//***************************************************************************** +//** +//** GAME_TALON.CPP +//** +//** Game code for Tommy's sidekick, Talon the Hawk +//** +//***************************************************************************** + +// HEADER FILES --------------------------------------------------------------- + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS --------------------------------------------------------------------- + +#define PERCH_ROTATION 3.5 +#define ROTATION_SPEED 3.5 +#define ROTATION_SPEED_FAST 5 +#define TALON_BLEND 100 + +// TYPES ---------------------------------------------------------------------- + +// CLASS DECLARATIONS --------------------------------------------------------- + +const idEventDef EV_PerchTicker("", NULL); +const idEventDef EV_PerchChatter("perchChatter", NULL); +const idEventDef EV_PerchSquawk("", NULL); + +const idEventDef EV_CheckForTarget( "", NULL ); +const idEventDef EV_CheckForEnemy( "", NULL ); + +const idEventDef EV_TalonAction("talonaction", "ed"); + +// Anim Events +const idEventDef EV_LandAnim("", NULL); +const idEventDef EV_PreLandAnim("", NULL); +const idEventDef EV_IdleAnim("", NULL); +const idEventDef EV_TommyIdleAnim("", NULL); +const idEventDef EV_FlyAnim("", NULL); +const idEventDef EV_GlideAnim("", NULL); +const idEventDef EV_TakeOffAnim("", NULL); +const idEventDef EV_TakeOffAnimB("", NULL); + +CLASS_DECLARATION( hhMonsterAI, hhTalon ) + EVENT(EV_PerchChatter, hhTalon::Event_PerchChatter) + EVENT(EV_PerchSquawk, hhTalon::Event_PerchSquawk) + EVENT(EV_CheckForTarget, hhTalon::Event_CheckForTarget) + EVENT(EV_CheckForEnemy, hhTalon::Event_CheckForEnemy) + + // Anims + EVENT(EV_LandAnim, hhTalon::Event_LandAnim) + EVENT(EV_PreLandAnim, hhTalon::Event_PreLandAnim) + EVENT(EV_IdleAnim, hhTalon::Event_IdleAnim) + EVENT(EV_TommyIdleAnim, hhTalon::Event_TommyIdleAnim) + EVENT(EV_FlyAnim, hhTalon::Event_FlyAnim) + EVENT(EV_GlideAnim, hhTalon::Event_GlideAnim) + EVENT(EV_TakeOffAnim, hhTalon::Event_TakeOffAnim) + EVENT(EV_TakeOffAnimB, hhTalon::Event_TakeOffAnimB) +END_CLASS + +const idEventDef EV_CallTalon("callTalon", "fff" ); +const idEventDef EV_ReleaseTalon("releaseTalon", NULL ); +const idEventDef EV_SetPerchState("setPerchState", "f" ); + +CLASS_DECLARATION( idEntity, hhTalonTarget ) + EVENT( EV_CallTalon, hhTalonTarget::Event_CallTalon ) + EVENT( EV_ReleaseTalon, hhTalonTarget::Event_ReleaseTalon ) + EVENT( EV_SetPerchState, hhTalonTarget::Event_SetPerchState ) +END_CLASS + +// STATE DECLARATIONS --------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES ----------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES ------------------------------------------------ + +// EXTERNAL DATA DECLARATIONS ------------------------------------------------- + +// PUBLIC DATA DEFINITIONS ---------------------------------------------------- + +// PRIVATE DATA DEFINITIONS --------------------------------------------------- + +// CODE ----------------------------------------------------------------------- + +//============================================================================= +// +// hhTalon::Spawn +// +//============================================================================= + +void hhTalon::Spawn(void) { + flyAnim = GetAnimator()->GetAnim("fly"); + glideAnim = GetAnimator()->GetAnim("glide"); + prelandAnim = GetAnimator()->GetAnim("preland"); + landAnim = GetAnimator()->GetAnim("land"); + idleAnim = GetAnimator()->GetAnim("idle"); + tommyIdleAnim = GetAnimator()->GetAnim("tommy_idle"); + squawkAnim = GetAnimator()->GetAnim("squawk"); + stepAnim = GetAnimator()->GetAnim("step"); + takeOffAnim = GetAnimator()->GetAnim("takeoffA"); + takeOffAnimB = GetAnimator()->GetAnim("takeoffB"); + attackAnim = GetAnimator()->GetAnim("attack1"); + preAttackAnim = GetAnimator()->GetAnim("preattack"); + + fl.neverDormant = true; + fl.takedamage = false; + fl.notarget = true; + + allowHiddenMovement = true; // Allow Talon to move while hidden + + health = 1; // Talon need some health for enemies to consider it alive + + //AOB + GetAnimator()->RemoveOriginOffset( true ); + + // Register the wings skins + openWingsSkin = declManager->FindSkin( spawnArgs.GetString("skin_openWings") ); + closedWingsSkin = declManager->FindSkin( spawnArgs.GetString("skin_closeWings") ); + + Event_SetMoveType(MOVETYPE_FLY); + + GetPhysics()->SetContents( 0 ); // No collisions + GetPhysics()->SetClipMask( 0 ); // No collisions + Hide(); + BecomeInactive(TH_THINK); +} + +void hhTalon::Save(idSaveGame *savefile) const { + owner.Save(savefile); + savefile->WriteVec3(velocity); + savefile->WriteVec3(acceleration); + + savefile->WriteObject(talonTarget); + savefile->WriteVec3(talonTargetLoc); + savefile->WriteMat3(talonTargetAxis); + savefile->WriteVec3(lastCheckOrigin); + + savefile->WriteFloat(checkTraceTime); + savefile->WriteFloat(checkFlyTime); + savefile->WriteFloat(flyStraightTime); + + savefile->WriteBool(bLanding); + savefile->WriteBool(bReturnToTommy); + savefile->WriteBool( bForcedTarget ); + savefile->WriteBool( bClawingAtEnemy ); + + savefile->WriteFloat( velocityFactor ); + savefile->WriteFloat( rotationFactor ); + savefile->WriteFloat( perchRotationFactor ); + + savefile->WriteInt(flyAnim); + savefile->WriteInt(glideAnim); + savefile->WriteInt(prelandAnim); + savefile->WriteInt(landAnim); + savefile->WriteInt(idleAnim); + savefile->WriteInt(tommyIdleAnim); + savefile->WriteInt(squawkAnim); + savefile->WriteInt(stepAnim); + savefile->WriteInt(takeOffAnim); + savefile->WriteInt(takeOffAnimB); + savefile->WriteInt(attackAnim); + savefile->WriteInt(preAttackAnim); + + savefile->WriteSkin(openWingsSkin); + savefile->WriteSkin(closedWingsSkin); + + enemy.Save( savefile ); + trailFx.Save( savefile ); + + savefile->WriteInt( state ); +} + +void hhTalon::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); + savefile->ReadVec3(velocity); + savefile->ReadVec3(acceleration); + + savefile->ReadObject( reinterpret_cast(talonTarget) ); + savefile->ReadVec3(talonTargetLoc); + savefile->ReadMat3(talonTargetAxis); + savefile->ReadVec3(lastCheckOrigin); + + savefile->ReadFloat(checkTraceTime); + savefile->ReadFloat(checkFlyTime); + savefile->ReadFloat(flyStraightTime); + + savefile->ReadBool(bLanding); + savefile->ReadBool(bReturnToTommy); + savefile->ReadBool( bForcedTarget ); + savefile->ReadBool( bClawingAtEnemy ); + + savefile->ReadFloat( velocityFactor ); + savefile->ReadFloat( rotationFactor ); + savefile->ReadFloat( perchRotationFactor ); + + savefile->ReadInt(flyAnim); + savefile->ReadInt(glideAnim); + savefile->ReadInt(prelandAnim); + savefile->ReadInt(landAnim); + savefile->ReadInt(idleAnim); + savefile->ReadInt(tommyIdleAnim); + savefile->ReadInt(squawkAnim); + savefile->ReadInt(stepAnim); + savefile->ReadInt(takeOffAnim); + savefile->ReadInt(takeOffAnimB); + savefile->ReadInt(attackAnim); + savefile->ReadInt(preAttackAnim); + + savefile->ReadSkin(openWingsSkin); + savefile->ReadSkin(closedWingsSkin); + + enemy.Restore( savefile ); + trailFx.Restore( savefile ); + + savefile->ReadInt( reinterpret_cast (state) ); +} + +//============================================================================= +// +// hhTalon::hhTalon +// +//============================================================================= + +hhTalon::hhTalon() { + owner = NULL; + talonTarget = NULL; + enemy.Clear(); + trailFx.Clear(); +} + +//============================================================================= +// +// hhTalon::~hhTalon +// +//============================================================================= + +hhTalon::~hhTalon() { + if( owner.IsValid() ) { + owner->talon = NULL; + } + + if ( trailFx.IsValid() ) { + trailFx->Nozzle( false ); + SAFE_REMOVE( trailFx ); + } +} + +//============================================================================= +// +// hhTalon::SummonTalon +// +//============================================================================= + +void hhTalon::SummonTalon(void) { + hhFxInfo fxInfo; + + state = StateNone; + + BecomeActive(TH_THINK); + Show(); + + checkTraceTime = spawnArgs.GetFloat( "traceCheckPulse", "0.1" ); + lastCheckOrigin = GetOrigin(); + + FindTalonTarget(NULL, NULL, true); + + CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved + SetOrigin(talonTargetLoc); + viewAxis = talonTargetAxis; + + bLanding = false; + + SetSkin( closedWingsSkin ); // The only time this is set in code, otherwise it's set by the land anim + + fl.neverDormant = true; + + SetForcedTarget( false ); + + velocityFactor = 1.0f; + rotationFactor = 1.0f; + perchRotationFactor = 1.0f; + + SetShaderParm( SHADERPARM_DIVERSITY, 0 ); + + EnterTommyState(); + TommyTicker(); // Force Talon to tick once to adjust his orientation +} + +//============================================================================= +// +// hhTalon::SetOwner +// +//============================================================================= + +void hhTalon::SetOwner( hhPlayer *newOwner ) { + owner = newOwner; + Event_SetOwner( newOwner ); +} + +void hhTalon::OwnerEnteredVehicle( void ) { + if ( talonTarget ) { + talonTarget->Left( this ); + } + + SummonTalon(); + EnterVehicleState(); +} + +void hhTalon::OwnerExitedVehicle( void ) { + ExitVehicleState(); + SummonTalon(); +} + +//============================================================================= +// +// hhTalon::FindTalonTarget +// +//============================================================================= + +void hhTalon::FindTalonTarget( idEntity *skipEntity, hhTalonTarget *forceTarget, bool bForcePlayer ) { + int i; + int count; + float dist; + float bestDist; + hhTalonTarget *ent; + idVec3 dir; + + talonTarget = NULL; + bestDist = spawnArgs.GetFloat( "perchDistance", "250" ); + + if ( owner->InVehicle() ) { + bestDist *= 10; + } + + if( bReturnToTommy) { + bForcePlayer = true; + } + + if ( forceTarget ) { + talonTarget = forceTarget; + } + + // Iterate through valid perch spots and find the best choice + if( !bForcePlayer && !forceTarget ) { + count = gameLocal.talonTargets.Num(); + for(i = 0; i < count; i++) { + ent = (hhTalonTarget *)gameLocal.talonTargets[i]; + + if ( !ent->bValidForTalon ) { // Not a valid target + continue; + } + + // ignore if there is no way we can see this perch spot + if( ent == skipEntity || !gameLocal.InPlayerPVS( ent ) || ent->priority == -1) { + continue; + } + + if ( owner.IsValid() && owner->IsSpiritOrDeathwalking() && ent->bNotInSpiritWalk ) { // If the owner is spiritwalking, then don't try to fly to this target (useful for lifeforce pickups) + continue; + } + + // Calculate the distance from the player to the perch spot + dist = ( owner->GetEyePosition() - ent->GetPhysics()->GetOrigin() ).Length(); + + // Adjust the distance based upon the priority (ranges from 0 - 2, -1 to completely skip). + dist *= ent->priority; + + if(dist > bestDist) { // Farther than the nearest perch spot + continue; + } + + talonTarget = ent; + bestDist = dist; + } + } + + if( !talonTarget && owner.IsValid() ) { // No perch spot, so use a spot close to the player + owner->GetJointWorldTransform( "FX_bird", talonTargetLoc, talonTargetAxis ); + talonTargetAxis = owner->viewAxis; + } else { + CalculateTalonTargetLocation(); + } +} + +//============================================================================= +// +// hhTalon::CalculateTalonTargetLocation +// +//============================================================================= + +void hhTalon::CalculateTalonTargetLocation() { + trace_t tr; + + if ( !talonTarget ) { + return; + } + + // Trace down a bit to get the exact perch location + if( gameLocal.clip.TracePoint( tr, talonTarget->GetPhysics()->GetOrigin() + idVec3(0, 0, 4), talonTarget->GetPhysics()->GetOrigin() - idVec3(0, 0, 16), MASK_SOLID, this )) { + talonTargetLoc = tr.endpos - idVec3( 0, 0, 1 ); // End position, minus a tiny bit so Talon perches on railings + } else { // No collision, just use the current floating spot + talonTargetLoc = talonTarget->GetPhysics()->GetOrigin(); + } + + talonTargetAxis = talonTarget->GetPhysics()->GetAxis(); +} + +//============================================================================= +// +// CheckReachedTarget +// +//============================================================================= + +bool hhTalon::CheckReachedTarget( float distance ) { + idVec3 vec; + + if(distance < spawnArgs.GetFloat( "distanceSlow", "80" ) ) { + if(talonTarget) { + velocity = (talonTargetLoc - GetOrigin()) * 0.05f; // Move more slowly when about to perch + + if( !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { + Event_PreLandAnim(); + StartSound( "snd_preland", SND_CHANNEL_BODY ); + } + bLanding = true; + } + + if(talonTarget) { + if(distance < spawnArgs.GetFloat( "distancePerchEpsilon", "3" ) ) { // Perch on this spot + Event_LandAnim(); + StartSound( "snd_land", SND_CHANNEL_BODY ); + + talonTarget->Reached(this); + EnterPerchState(); + + return true; // No need to continue the fly ticker + } + } else if(distance < spawnArgs.GetFloat( "distanceTommyEpsilon", "15.0" ) ) { // No perch spot, so perch on Tommy around this location + Event_LandAnim(); + EnterTommyState(); + return true; // No need to continue the fly ticker + } + } else if(distance >= spawnArgs.GetFloat( "distanceTurbo", "2500" ) ) { // Distance is so far, we should kick Talon into turbo mode + velocity *= 10; + } + + return false; +} + +//============================================================================= +// +// hhTalon::CheckCollisions +// +//============================================================================= + +void hhTalon::CheckCollisions(float deltaTime) { + idVec3 oldOrigin; + trace_t tr; + + checkTraceTime -= deltaTime; + + if(checkTraceTime > 0) { + return; + } + + checkTraceTime = spawnArgs.GetFloat( "traceCheckPulse", "0.1" ); + + idVec3 nextLocation = GetOrigin() + this->velocity * 0.5f; + + // Trace to determine if the hawk is just about to fly into a wall + if ( gameLocal.clip.TracePoint( tr, GetOrigin(), nextLocation, MASK_SOLID, owner.GetEntity() ) ) { // Struck something solid + // Glow + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + } else if ( gameLocal.clip.TracePoint( tr, GetOrigin(), lastCheckOrigin, MASK_SOLID, owner.GetEntity() ) ) { // Exited something solid + // Glow + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + } + + lastCheckOrigin = GetOrigin(); +} + +//============================================================================= +// +// hhTalon::Think +// +//============================================================================= + +void hhTalon::Think(void) { + + if ( owner->InGravityZone() ) { // don't perch in gravity zones, unless gravity is "normal" + idVec3 gravity = owner->GetGravity(); + gravity.Normalize(); + + float dot = gravity * idVec3( 0.0f, 0.0f, -1.0f ); + if ( dot < 1.0f ) { + if ( talonTarget ) { + talonTarget->Left( this ); + } + + ReturnToTommy(); + } + } + + // Shadow suppression based upon the player + if ( g_showPlayerShadow.GetBool() ) { + renderEntity.suppressShadowInViewID = 0; + } + else { + renderEntity.suppressShadowInViewID = owner->entityNumber + 1; + } + + hhMonsterAI::Think(); +} + +//============================================================================= +// +// hhTalon::FlyMove +// +//============================================================================= + +void hhTalon::FlyMove( void ) { + // Run the specific task Ticker + switch( state ) { + case StateTommy: + TommyTicker(); + break; + case StateFly: + FlyTicker(); + break; + case StateVehicle: + VehicleTicker(); + break; + case StatePerch: + PerchTicker(); + break; + case StateAttack: + AttackTicker(); + break; + } + + // run the physics for this frame + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( disableGravity ); + RunPhysics(); +} + +//============================================================================= +// +// hhTalon::AdjustFlyingAngles +// +// No need to adjust flying angles on Talon +//============================================================================= + +void hhTalon::AdjustFlyingAngles() { + return; +} + +//============================================================================= +// +// hhTalon::GetPhysicsToVisualTransform +// +//============================================================================= + +bool hhTalon::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = modelOffset; + if ( GetBindMaster() && bindJoint != INVALID_JOINT ) { + idMat3 masterAxis; + idVec3 masterOrigin; + GetMasterPosition( masterOrigin, masterAxis ); + + origin = masterOrigin + physicsObj.GetLocalOrigin() * masterAxis; + axis = GetBindMaster()->GetAxis(); + } else { + axis = viewAxis; + } + return true; +} + +//============================================================================= +// +// hhTalon::CrashLand +// +// No need to check crash landing on Talon +//============================================================================= + +void hhTalon::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + return; +} + +//============================================================================= +// +// hhTalon::Damage +// +// Talon cannot be damaged +//============================================================================= + +void hhTalon::Damage( idEntity *inflictor, idEntity *attack, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + return; +} + +//============================================================================= +// +// hhTalon::Killed +// +// Talon cannot be killed +//============================================================================= + +void hhTalon::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + return; +} + +//============================================================================= +// +// hhTalon::Portalled +// +//============================================================================= + +void hhTalon::Portalled( idEntity *portal ) { + if ( state == StateTommy ) { // No need to portal if Talon is already on Tommy's shoulder + return; + } + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); + + // If Talon is currently on a gui, inform the gui he is leaving + if ( talonTarget && ( state == StatePerch || state == StateVehicle ) ) { // CJR PCF 050306: Only call Left() if Talon is perching on a console + talonTarget->Left( this ); + } + + StopAttackFX(); + + // Force Talon to Tommy's shoulder after he portals + talonTarget = NULL; + bLanding = false; + SetSkin( openWingsSkin ); // The only time this is set in code, otherwise it's set by the land anim + Event_LandAnim(); + SetForcedTarget( false ); + EnterTommyState(); +} + +//============================================================================= +// +// hhTalon::EnterFlyState +// +//============================================================================= + +void hhTalon::EnterFlyState(void) { + EndState(); + state = StateFly; + checkFlyTime = spawnArgs.GetFloat( "checkFlyTime", "4.0" ); + flyStraightTime = spawnArgs.GetFloat( "flyStraightTime", "0.5" ); + bLanding = false; + + SetShaderParm( SHADERPARM_DIVERSITY, 0 ); // Talon's model should phase out if the player is too close + + +} + +//============================================================================= +// +// hhTalon::FlyTicker +// +//============================================================================= + +void hhTalon::FlyTicker(void) { + idAngles ang; + float distance; + float deltaTime = MS2SEC(gameLocal.msec); + idVec3 oldVel; + float distanceXY; + float deltaZ; + float rotSpeed; + + // If the bird is currently in fly mode, but hasn't finished the takeoff anim, then don't let the bird fly around + if( GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( takeOffAnim ) ) ) { + return; + } + + acceleration = talonTargetLoc - GetOrigin(); + + idVec2 vecXY = acceleration.ToVec2(); + distance = acceleration.Normalize(); + + distanceXY = vecXY.Length(); + deltaZ = talonTargetLoc.z - GetOrigin().z; + + float dp = acceleration * GetAxis()[0]; + float side = acceleration * GetAxis()[1]; + ang = GetAxis().ToAngles(); + + // Talon can either rotate towards the target, or fly straight. + if( flyStraightTime > 0 ) { // Continue to fly straight + flyStraightTime -= deltaTime; + + if ( flyStraightTime <= 0 && !bLanding ) { // No longer flying straight + Event_FlyAnim(); + } + + rotSpeed = 0; + } else if( bLanding ) { // About to land, so turn slightly faster than normal + rotSpeed = ROTATION_SPEED_FAST * rotationFactor; + } else { + rotSpeed = ROTATION_SPEED * rotationFactor; + } + + + rotSpeed *= (60.0f * USERCMD_ONE_OVER_HZ); + + // Determine whether Talon should rotate left or right to reach the target + if(dp > 0) { + if(dp >= 0.98f || ( side <= 0.1f && side >= 0.1f )) { // CJR PCF 04/28/06: don't turn if directly facing the target + rotSpeed = 0; + } else if (side < -0.1f) { + rotSpeed *= -1.0f; + } + } else { + if(side < 0) { + rotSpeed *= -1.0f; + } + } + + // Apply rotation + deltaViewAngles.yaw = rotSpeed; + oldVel = velocity; + + float defaultVelocityXY = spawnArgs.GetFloat( "velocityXY", "7.0" ); + float defaultVelocityZ = spawnArgs.GetFloat( "velocityZ", "4" ); + + velocity = viewAxis[0] * defaultVelocityXY * velocityFactor; // xy-velocity + + // Z-change based upon distance + if(abs(deltaZ) > 0 && distanceXY > 0) { + velocity.z = (deltaZ * defaultVelocityXY) / distanceXY; + + // Clamp z velocity + if(velocity.z > defaultVelocityZ) { + velocity.z = defaultVelocityZ; + } + } + + if ( CheckReachedTarget( distance ) ) { + return; + } + + // Apply velocity + SetOrigin( GetOrigin() + velocity * (60.0f * USERCMD_ONE_OVER_HZ) ); + + // Check for colliding with world surfaces (only if not currently trying to land) + if( !bLanding ) { + CheckCollisions(deltaTime); + } + + // Timeout for flying to perch spot... if too much time has passed, then fly straight for a bit + checkFlyTime -= deltaTime; + if( checkFlyTime <= 0 && !bLanding ) { + flyStraightTime = spawnArgs.GetFloat( "flyStraightTime", "0.5" ); + checkFlyTime = spawnArgs.GetFloat( "checkFlyTime", "4.0" ); + + PostEventMS( &EV_GlideAnim, 0 ); + } + + // If no talonTarget (probably flying towards Tommy), then constantly look for a new target + if( !bForcedTarget && !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { + FindTalonTarget( NULL, NULL, false ); + } +} + +//============================================================================= +// +// hhTalon::EnterTommyState +// +//============================================================================= + +void hhTalon::EnterTommyState(void) { + idVec3 bindOrigin; + idMat3 bindAxis; + + bReturnToTommy = false; + + EndState(); + state = StateTommy; + + Hide(); + + UpdateVisuals(); + + // Bind the bird to Tommy's shoulder + owner->GetJointWorldTransform( "fx_bird", bindOrigin, bindAxis ); + SetOrigin( bindOrigin ); + SetAxis( owner->viewAxis ); + BindToJoint( owner.GetEntity(), "fx_bird", true ); + + // Start checking for enemies + PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); + + // Start checking for nearby targets + PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); +} + +//============================================================================= +// +// hhTalon::EndState +// +//============================================================================= + +void hhTalon::EndState(void) { + Unbind(); + + int oldState = state; + state = StateNone; + + if ( fl.hidden ) { // If the bird is hidden, show it when ending the state + Show(); + } + + // Zero out Talon's velocity when initially taking off, so it doesn't inherit any from the bind master + velocity = vec3_zero; + GetPhysics()->SetLinearVelocity( vec3_zero ); + + // Cancel any checks for enemies + CancelEvents( &EV_CheckForEnemy ); + CancelEvents( &EV_CheckForTarget ); + + StopAttackFX(); + + if( oldState == StateTommy ) { + GetPhysics()->SetAxis( mat3_identity ); + + // Show the hawk in the player's view after it takes off + Show(); + } else if( oldState == StatePerch ) { + CancelEvents( &EV_PerchSquawk ); + } else if ( oldState == StateAttack ) { + // Clear the enemy + if ( enemy.IsValid() ) { + (static_cast(enemy.GetEntity()))->Distracted( GetOwner() ); // Set the enemy's enemy back to Tommy + enemy.Clear(); + } + } +} + +//============================================================================= +// +// hhTalon::TommyTicker +// +//============================================================================= + +void hhTalon::TommyTicker(void) { + idVec3 viewOrigin; + idMat3 viewAxis; + idMat3 ownerAxis; + float deltaTime = MS2SEC(gameLocal.msec); + + bLanding = false; + + // Turn towards direction of Tommy + idVec3 toFace = owner->GetAxis()[0]; + toFace.z = 0; + toFace.Normalize(); + float dp = toFace * GetAxis()[0]; + float side = toFace * GetAxis()[1]; + + if(side > 0.05 || dp < 0 ) { + deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; + UpdateVisuals(); + } else if (side < -0.05) { + deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; + UpdateVisuals(); + } +} + +//============================================================================= +// +// hhTalon::FindEnemy +// +//============================================================================= + +bool hhTalon::FindEnemy( void ) { + idEntity *ent; + hhMonsterAI *actor; + hhMonsterAI *bestEnemy; + float bestDist; + float maxEnemyDist; + float dist; + idVec3 delta; + int enemyCount; + + // Check if Talon already has a valid enemy + if ( enemy.IsValid() && enemy->health > 0 ) { + return true; + } + + bestDist = 0; + maxEnemyDist = 1024.0f * 1024.0f; // sqr + bestEnemy = NULL; + enemyCount = 0; + for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( hhMonsterAI::Type ) ) { + continue; + } + + actor = static_cast( ent ); + + // Only attack living enemies, and only attack enemies that are targetting Tommy or Talon + if ( (actor->health <= 0) || ( actor->GetEnemy() != GetOwner() && actor->GetEnemy() != this ) ) { + continue; + } + + if ( !gameLocal.InPlayerPVS( actor ) ) { + continue; + } + + // Check if Talon should avoid this enemy -- these enemies are also not included in the enemy count + if ( !ent->spawnArgs.GetBool( "bTalonAttack", "0" ) ) { + continue; + } + + delta = GetOwner()->GetOrigin() - actor->GetPhysics()->GetOrigin(); // Find the farthest enemy (in the PVS) to Tommy + dist = delta.LengthSqr(); + if ( dist > bestDist && dist < maxEnemyDist && GetOwner()->CanSee( actor, false ) ) { + bestDist = dist; + bestEnemy = actor; + } + + enemyCount++; + } + if ( bestEnemy && enemyCount >= 2 ) { // Only attack if more than one enemy is attacking the player + enemy = bestEnemy; + return true; + } + + enemy.Clear(); + return false; +} + +//============================================================================= +// +// hhTalon::EnterPerchState +// +//============================================================================= + +void hhTalon::EnterPerchState(void) { + EndState(); + state = StatePerch; + + velocity = vec3_zero; + + if ( talonTarget ) { + CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved + SetOrigin( talonTargetLoc ); + + if ( talonTarget->IsBound() ) { // Only bind talon to this target, if it's bound to something + Bind( talonTarget, true ); + } + + GetPhysics()->SetLinearVelocity( vec3_zero ); + } + + if ( talonTarget && talonTarget->bShouldSquawk ) { + float frequency = talonTarget->spawnArgs.GetFloat( "squawkFrequency", "5.0" ); + PostEventSec( &EV_PerchSquawk, frequency ); + } + + SetShaderParm( SHADERPARM_DIVERSITY, 1 ); // Talon's model shouldn't phase out if the player is too close + + // Start checking for nearby enemies + PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); + + // Start checking for nearby targets + PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); +} + +//============================================================================= +// +// hhTalon::PerchTicker +// +//============================================================================= + +void hhTalon::PerchTicker(void) { + float deltaTime = MS2SEC(gameLocal.msec); + + bLanding = false; + + // Turn towards direction of perch spot + idVec3 toFace = talonTarget->GetPhysics()->GetAxis()[0]; + toFace.z = 0; + toFace.Normalize(); + float dp = toFace * GetAxis()[0]; + float side = toFace * GetAxis()[1]; + + if ( dp < 0 ) { + if ( side >= 0 ) { + deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; + } else { + deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; + } + } else if (side > 0.05 ) { + deltaViewAngles.yaw = PERCH_ROTATION * perchRotationFactor; + } else if (side < -0.05) { + deltaViewAngles.yaw = -PERCH_ROTATION * perchRotationFactor; + } + + UpdateVisuals(); +} + +//============================================================================= +// +// hhTalon::Event_PerchChatter +// +// Small chirping noise which is part of Talon's idle animation +//============================================================================= + +void hhTalon::Event_PerchChatter(void) { + if ( state != StateTommy ) { // Don't chatter when on Tommy's shoulder + StartSound( "snd_chatter", SND_CHANNEL_VOICE ); + } +} + +//============================================================================= +// +// hhTalon::Event_PerchSquawk +// +// Louder, squawking noise, alerting the player to this location +//============================================================================= + +void hhTalon::Event_PerchSquawk(void) { + if ( !talonTarget ) { + return; + } + + if ( talonTarget->bShouldSquawk ) { // Always check, because it could have been turned off after Talon perched + + // Check if the player is outside of the squawk distance + float squawkDistSqr = talonTarget->spawnArgs.GetFloat( "squawkDistance", "0" ); + squawkDistSqr *= squawkDistSqr; + + if ( (GetOrigin() - owner->GetOrigin() ).LengthSqr() > squawkDistSqr ) { // Far enough away, so squawk + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, squawkAnim, gameLocal.time, TALON_BLEND); + PostEventMS( &EV_IdleAnim, GetAnimator()->GetAnim( squawkAnim )->Length() + TALON_BLEND ); + StartSound( "snd_squawk", SND_CHANNEL_VOICE ); + + // Glow + SetShaderParm( SHADERPARM_TIMEOFFSET, MS2SEC( gameLocal.time ) ); + } + + // Post the next squawk attempt + float frequency = talonTarget->spawnArgs.GetFloat( "squawkFrequency", "5.0" ); + PostEventSec( &EV_PerchSquawk, frequency + frequency * gameLocal.random.RandomFloat() ); + } +} + +//============================================================================= +// +// hhTalon::Event_CheckForTarget +// +//============================================================================= + +void hhTalon::Event_CheckForTarget() { + hhTalonTarget *oldTarget = talonTarget; + + // Determine if Talon should take off from his perch + if ( !bForcedTarget ) { // do not check if Talon is forced this this point + FindTalonTarget(NULL, NULL, false); + + if( oldTarget != talonTarget ) { + Event_TakeOffAnim(); + + if( oldTarget ) { // Inform the perch spot that talon is leaving + oldTarget->Left(this); + } + + EnterFlyState(); + return; + } + } + + PostEventSec( &EV_CheckForTarget, spawnArgs.GetFloat( "checkTargetTime", "1.0" ) ); +} + +//============================================================================= +// +// hhTalon::Event_CheckForEnemy +// +//============================================================================= + +void hhTalon::Event_CheckForEnemy() { + if ( !ai_talonAttack.GetBool() ) { + return; + } + if ( !bForcedTarget ) { + if ( FindEnemy() ) { + Event_TakeOffAnim(); + + if( talonTarget ) { // Inform the perch spot that talon is leaving + talonTarget->Left( this ); + } + + EnterAttackState(); + return; + } + } + + PostEventSec( &EV_CheckForEnemy, spawnArgs.GetFloat("checkEnemyTime", "4.0" ) ); +} + +//============================================================================= +// +// hhTalon::EnterAttackState +// +//============================================================================= + +void hhTalon::EnterAttackState(void) { + EndState(); + state = StateAttack; + checkFlyTime = spawnArgs.GetFloat( "attackTime", "8.0f" ); + + StartSound( "snd_attack", SND_CHANNEL_BODY ); + + bClawingAtEnemy = false; + + StartAttackFX(); +} + +//============================================================================= +// +// hhTalon::AttackTicker +// +//============================================================================= + +void hhTalon::AttackTicker(void) { + float deltaTime = MS2SEC(gameLocal.msec); + + if ( !FindEnemy() ) { + ReturnToTommy(); + StopAttackFX(); + return; + } + + idAngles ang; + float distance; + idVec3 oldVel; + bool landing = false; + float distanceXY; + float deltaZ; + float rotSpeed; + float distToEnemy; + + idMat3 junkAxis; + enemy->GetViewPos(talonTargetLoc, junkAxis); + talonTargetLoc -= junkAxis[2] * 10.0f; + + distToEnemy = (talonTargetLoc - GetOrigin()).Length(); + + acceleration = talonTargetLoc - GetOrigin(); + + idVec2 vecXY = acceleration.ToVec2(); + distance = acceleration.Normalize(); + + distanceXY = vecXY.Length(); + deltaZ = talonTargetLoc.z - GetOrigin().z; + + float dp = acceleration * GetAxis()[0]; + float side = acceleration * GetAxis()[1]; + ang = GetAxis().ToAngles(); + + // Talon can either rotate towards the target, or fly straight. + if( flyStraightTime > 0 ) { // Continue to fly straight + flyStraightTime -= deltaTime; + + if ( flyStraightTime <= 0 ) { // Done gliding, play a flap animation + Event_FlyAnim(); + + if ( bClawingAtEnemy ) { // Done attacking + bClawingAtEnemy = false; + + flyStraightTime = 1.0f; + } + } + + rotSpeed = 0; + } else { + rotSpeed = ROTATION_SPEED * rotationFactor; + } + + rotSpeed *= (60.0f * USERCMD_ONE_OVER_HZ); // CJR PCF 04/28/06: Adjust for 30Hz + + // Determine whether Talon should rotate left or right to reach the target + if(dp > 0) { + if(dp >= 0.98f || ( side <= 0.1f && side >= 0.1f )) { // CJR PCF 04/28/06: don't turn if directly facing the target + rotSpeed = 0; + } else if (side < -0.1f) { + rotSpeed *= -1; + } + } else { + if(side < 0) { + rotSpeed *= -1; + } + } + + // Apply rotation + deltaViewAngles.yaw = rotSpeed; + float desiredRoll = -side * 20 * rotSpeed; + + oldVel = velocity; + + float defaultVelocityXY = spawnArgs.GetFloat( "velocityXY_attack", "8.5" ); + float defaultVelocityZ = spawnArgs.GetFloat( "velocityZ", "4" ); + + // Adjust velocity based upon the attack + if ( bClawingAtEnemy ) { + defaultVelocityXY = 0.0f; + + idVec3 tempVec( vecXY.x, vecXY.y, 0.0f ); + viewAxis = tempVec.ToMat3(); + + } else if ( flyStraightTime > 0 ) { // Flying away from the enemy + float adjust = hhMath::ClampFloat( 0.0f, 1.0f, 2.0f - flyStraightTime * 2.0f ); + defaultVelocityXY *= adjust; // Move more slowly when about to attack + } else { // Flying around, check if the bird should slow down when nearing the enemy + if ( distToEnemy < 150.0f && dp > 0 ) { // Close to the enemy and facing it + if( !GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( preAttackAnim ) ) ) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, preAttackAnim, gameLocal.time, TALON_BLEND); + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); + } + float adjust = 1.0f - hhMath::ClampFloat( 0.0f, 0.9f, (100 - (distToEnemy-50) ) / 100.0f); // Decelerate Talon + defaultVelocityXY *= adjust; + } + } + + velocity = viewAxis[0] * defaultVelocityXY * velocityFactor; // xy-velocity + + // Z-change based upon distance + if(abs(deltaZ) > 0 && distanceXY > 0) { + velocity.z = (deltaZ * defaultVelocityXY) / distanceXY; + + // Clamp z velocity + if(velocity.z > defaultVelocityZ) { + velocity.z = defaultVelocityZ; + } + } + + // Apply velocity + SetOrigin( GetOrigin() + velocity ); + + UpdateVisuals(); + + // Timeout for flying to perch spot... if too much time has passed, then fly straight for a bit + checkFlyTime -= deltaTime; + if( checkFlyTime <= 0 && !bClawingAtEnemy && flyStraightTime <= 0 ) { + ReturnToTommy(); + StopAttackFX(); + } + + // Check if Talon reached the target and attack! + if ( flyStraightTime <= 0 && ( distToEnemy < spawnArgs.GetFloat( "distanceAttackEpsilon", "60" ) ) ) { + // Damage the enemy + StartSound( "snd_attack", SND_CHANNEL_BODY ); + + // Distract the enemy by setting Talon as its enemy -- random chance that this will succeed + if ( enemy.IsValid() && gameLocal.random.RandomFloat() < spawnArgs.GetFloat( "attackChance", "0.5" ) ) { + (static_cast(enemy.GetEntity()))->Distracted( this ); + owner->TalonAttackComment(); + } + + // Play an attack anim here + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, attackAnim, gameLocal.time, 0 ); + + flyStraightTime = MS2SEC( GetAnimator()->GetAnim( attackAnim )->Length() ); + PostEventSec( &EV_FlyAnim, flyStraightTime ); // Play a fly anim once this attack animation is done + + bClawingAtEnemy = true; + } +} + +//============================================================================= +// +// hhTalon::StartAttackFX +// +//============================================================================= + +void hhTalon::StartAttackFX() { + SetShaderParm( SHADERPARM_MODE, 1 ); // Fire glow attack + + if ( trailFx.IsValid() ) { // An effect already exists, turn it on + trailFx->Nozzle( true ); + } else { // Spawn a new effect + const char *defName = spawnArgs.GetString( "fx_attackTrail" ); + if (defName && defName[0]) { + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + trailFx = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient ); + if (trailFx.IsValid()) { + trailFx->fl.neverDormant = true; + trailFx->fl.networkSync = false; + trailFx->fl.clientEvents = true; + } + } + } +} + +//============================================================================= +// +// hhTalon::StopAttackFX +// +//============================================================================= + +void hhTalon::StopAttackFX() { + SetShaderParm( SHADERPARM_MODE, 0 ); // Fire glow attack done + + if ( trailFx.IsValid() ) { + trailFx->Nozzle( false ); + } +} + +//============================================================================= +// +// hhTalon::EnterVehicleState +// +//============================================================================= + +void hhTalon::EnterVehicleState(void) { + EndState(); + state = StateVehicle; + Hide(); + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); +} + +//============================================================================= +// +// hhTalon::ExitVehicleState +// +//============================================================================= + +void hhTalon::ExitVehicleState(void) { + if ( talonTarget ) { + talonTarget->Left( this ); + } +} + +//============================================================================= +// +// hhTalon::VehicleTicker +// +// How Talon acts when the player is in a vehicle: +// - In the interest of speed, Talon warps from target to Target +// - This allows for the giant translation GUIs to instantly translate as the player gets near +//============================================================================= + +void hhTalon::VehicleTicker(void) { + float deltaTime = MS2SEC(gameLocal.msec); + hhTalonTarget *oldEnt = talonTarget; + + if( GetAnimator()->IsAnimPlaying( GetAnimator()->GetAnim( prelandAnim ) ) ) { + return; + } + + FindTalonTarget( NULL, NULL, bReturnToTommy ); + + if ( talonTarget == oldEnt ) { // No change to the target, so do nothing + return; + } + + if ( !talonTarget ) { // No Talon Target, so hide Talon + Hide(); + } else { // have a target, to show the bird + Show(); + } + + // Have a target, so spawn the bird at the target, if it isn't already there + CalculateTalonTargetLocation(); // Must recalculate the target location, as the target may have moved + SetOrigin( talonTargetLoc ); + SetAxis( talonTargetAxis ); + UpdateVisuals(); + + SetSkin( openWingsSkin ); + Event_LandAnim(); + + // Trigger the target + if ( talonTarget ) { + talonTarget->Reached( this ); // Reached the spot, but fly through it + } + + // Inform the last talonTarget that Talon has left + if ( oldEnt ) { + oldEnt->Left( this ); + } +} + +//============================================================================= +// +// hhTalon::UpdateAnimationControllers +// Talon doesn't use any anim controllers +//============================================================================= + +bool hhTalon::UpdateAnimationControllers() { + idVec3 vel; + float speed; + float roll; + + speed = velocity.Length(); + if ( speed < 3.0f || flyStraightTime > 0 ) { + roll = 0.0f; + } else { + roll = acceleration * viewAxis[1] * -fly_roll_scale / fly_speed; + if ( roll > fly_roll_max ) { + roll = fly_roll_max; + } else if ( roll < -fly_roll_max ) { + roll = -fly_roll_max; + } + } + + fly_roll = fly_roll * 0.95f + roll * 0.05f; + + viewAxis = idAngles( 0, current_yaw, fly_roll ).ToMat3(); + + return false; +} + +//============================================================================= +// +// hhTalon::ReturnToTommy +// +//============================================================================= + +void hhTalon::ReturnToTommy( void ) { + if ( bReturnToTommy ) { // already returning + return; + } + + bReturnToTommy = true; + bLanding = false; + + FindTalonTarget(NULL, NULL, true); + + SetSkin( openWingsSkin ); + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, flyAnim, gameLocal.time, TALON_BLEND); + EnterFlyState(); +} + +// ANIM EVENTS ================================================================ + +//============================================================================= +// +// hhTalon::Event_LandAnim +// +//============================================================================= + +void hhTalon::Event_LandAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, landAnim, gameLocal.time, TALON_BLEND); + + if ( talonTarget ) { // Different animation if landing on Tommy or not + PostEventMS( &EV_IdleAnim, GetAnimator()->GetAnim( landAnim )->Length() + TALON_BLEND ); + } else { // No target, so the bird must be landing on Tommy + PostEventMS( &EV_TommyIdleAnim, GetAnimator()->GetAnim( landAnim )->Length() + TALON_BLEND ); + } + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); +} + +//============================================================================= +// +// hhTalon::Event_PreLandAnim +// +//============================================================================= + +void hhTalon::Event_PreLandAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, prelandAnim, gameLocal.time, TALON_BLEND ); + + CancelEvents( &EV_GlideAnim ); // Could possibly have a glide anim queue + CancelEvents( &EV_TakeOffAnimB ); +} + +//============================================================================= +// +// hhTalon::Event_IdleAnim +// +//============================================================================= + +void hhTalon::Event_IdleAnim(void) { + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.time, TALON_BLEND); + + StartSound("snd_idle", SND_CHANNEL_BODY); +} + +//============================================================================= +// +// hhTalon::Event_TommyIdleAnim +// +//============================================================================= + +void hhTalon::Event_TommyIdleAnim(void) { + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, tommyIdleAnim, gameLocal.time, TALON_BLEND); +} + +//============================================================================= +// +// hhTalon::Event_FlyAnim +// +//============================================================================= + +void hhTalon::Event_FlyAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, TALON_BLEND); + + StartSound("snd_fly", SND_CHANNEL_BODY); +} + +//============================================================================= +// +// hhTalon::Event_GlideAnim +// +//============================================================================= + +void hhTalon::Event_GlideAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, TALON_BLEND ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, glideAnim, gameLocal.time, TALON_BLEND); + + StartSound("snd_glide", SND_CHANNEL_BODY); +} + +//============================================================================= +// +// hhTalon::Event_TakeOffAnim +// +//============================================================================= + +void hhTalon::Event_TakeOffAnim(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); // No blend when clearing old anims while taking off + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, takeOffAnim, gameLocal.time, TALON_BLEND); + + PostEventMS( &EV_TakeOffAnimB, GetAnimator()->GetAnim( takeOffAnim )->Length() + TALON_BLEND ); + + StartSound("snd_takeoff", SND_CHANNEL_ANY); +} + +//============================================================================= +// +// hhTalon::Event_TakeOffAnimB +// +//============================================================================= + +void hhTalon::Event_TakeOffAnimB(void) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, takeOffAnimB, gameLocal.time, 0); + + PostEventMS( &EV_GlideAnim, GetAnimator()->GetAnim( takeOffAnimB )->Length() + TALON_BLEND ); +} + +//============================================================================= +// +// hhTalon::Show +// +// Special show needed to remove any collision information on Talon +//============================================================================= + +void hhTalon::Show() { + if ( state == StateTommy ) { + return; + } + + hhMonsterAI::Show(); + + // Needed after the show + GetPhysics()->SetContents( 0 ); // No collisions + GetPhysics()->SetClipMask( 0 ); // No collisions +} + +// TALON PERCH SPOT =========================================================== + +//============================================================================= +// +// hhTalonTarget::Spawn +// +//============================================================================= + +void hhTalonTarget::Spawn(void) { + priority = 2.0f - spawnArgs.GetInt("priority"); + if ( priority < 0 ) { + priority = 0; + } else if ( priority > 2) { + priority = 2; + } + + bNotInSpiritWalk = spawnArgs.GetBool( "notInSpiritWalk" ); + bShouldSquawk = spawnArgs.GetBool( "shouldSquawk" ); + bValidForTalon = spawnArgs.GetBool( "valid", "1" ); + + gameLocal.RegisterTalonTarget(this); + + GetPhysics()->SetContents( 0 ); + + Hide(); +} + +void hhTalonTarget::Save(idSaveGame *savefile) const { + savefile->WriteInt( priority ); + savefile->WriteBool( bNotInSpiritWalk ); + savefile->WriteBool( bShouldSquawk ); + savefile->WriteBool( bValidForTalon ); +} + +void hhTalonTarget::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( priority ); + savefile->ReadBool( bNotInSpiritWalk ); + savefile->ReadBool( bShouldSquawk ); + savefile->ReadBool( bValidForTalon ); +} + +//============================================================================= +// +// hhTalonTarget::ShowTargets +// +//============================================================================= + +void hhTalonTarget::ShowTargets( void ) { + int i; + int count; + idEntity *ent; + + count = gameLocal.talonTargets.Num(); + for(i = 0; i < count; i++) { + ent = (hhTalonTarget *)gameLocal.talonTargets[i]; + ent->Show(); + } +} + +//============================================================================= +// +// hhTalonTarget::HideTargets +// +//============================================================================= + +void hhTalonTarget::HideTargets( void ) { + int i; + int count; + idEntity *ent; + + count = gameLocal.talonTargets.Num(); + for(i = 0; i < count; i++) { + ent = (hhTalonTarget *)gameLocal.talonTargets[i]; + ent->Hide(); + } +} + +//============================================================================= +// +// hhTalonTarget::~hhTalonTarget +// +//============================================================================= + +hhTalonTarget::~hhTalonTarget() { + gameLocal.talonTargets.Remove( this ); +} + +//============================================================================= +// +// hhTalonTarget::Reached +// +// Called when Talon reaches the target point +//============================================================================= + +void hhTalonTarget::Reached( hhTalon *talon ) { + if( !talon ) { + return; + } + + // Landing on a perch spot sends an action message to the spot's targets + int num = targets.Num(); + for (int ix=0; ixPostEventMS(&EV_TalonAction, 0, talon, true); + } + } +} + +//============================================================================= +// +// hhTalonTarget::Left +// +// Called when Talon leaves the target point +//============================================================================= + +void hhTalonTarget::Left( hhTalon *talon ) { + if( !talon ) { + return; + } + + // Leaving on a perch spot sends an action message to the spot's targets + int num = targets.Num(); + for (int ix=0; ixPostEventMS(&EV_TalonAction, 0, talon, false); + } + } +} + +//============================================================================= +// +// hhTalonTarget::Event_CallTalon +// +// Calls Talon to this point and forces him to stay on this point unless released +// or called to another point +//============================================================================= + +void hhTalonTarget::Event_CallTalon( float vel, float rot, float perch ) { + hhPlayer *player; + hhTalon *talon; + + // Find Talon + player = static_cast( gameLocal.GetLocalPlayer() ); + if ( !player->talon.IsValid() ) { + return; + } + + talon = player->talon.GetEntity(); + + talon->SetForcedTarget( true ); + + talon->SetForcedPhysicsFactors( vel, rot, perch ); + + talon->FindTalonTarget( NULL, this, false ); // Force this entity as the target + talon->Event_TakeOffAnim(); + + talon->EnterFlyState(); +} + +//============================================================================= +// +// hhTalonTarget::Event_ReleaseTalon +// +// Releases Talon from this perch spot and allows him to continue his normal scripting +//============================================================================= + +void hhTalonTarget::Event_ReleaseTalon() { + hhPlayer *player; + hhTalon *talon; + + // Find Talon + player = static_cast( gameLocal.GetLocalPlayer() ); + if ( !player->talon.IsValid() ) { + return; + } + + talon = player->talon.GetEntity(); + + talon->SetForcedTarget( false ); + talon->SetForcedPhysicsFactors( 1.0f, 1.0f, 1.0f ); +} + +//============================================================================= +// +// hhTalonTarget::Event_SetPerchState +// +// Set the state if Talon can or cannot fly towards this target +//============================================================================= + +void hhTalonTarget::Event_SetPerchState( float newState ) { + bValidForTalon = ( newState > 0 ) ? true : false; +} diff --git a/neo/prey/Prey/game_talon.h b/neo/prey/Prey/game_talon.h new file mode 100644 index 00000000..c69bbfea --- /dev/null +++ b/neo/prey/Prey/game_talon.h @@ -0,0 +1,194 @@ + +#ifndef __GAME_TALON_H__ +#define __GAME_TALON_H__ + +#define MAX_TALON_PTS 16 + +typedef enum talontargetType_s { + TTT_NORMAL, + TTT_IMPORTANT, + TTT_BENEFICIAL +} talonTargetType_t; + +extern const idEventDef EV_TalonAction; + +class hhTalonTarget; + +//----------------------------------------------------------------------------- + +class hhTalon : public hhMonsterAI { +public: + CLASS_PROTOTYPE(hhTalon); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + hhTalon(); + ~hhTalon(); + void SummonTalon(void); + void OwnerEnteredVehicle( void ); + void OwnerExitedVehicle( void ); + + void SetOwner( hhPlayer *newOwner ); + hhPlayer *GetOwner( void ) { return owner.GetEntity(); } + void FindTalonTarget( idEntity *skipEntity, hhTalonTarget *forceTarget, bool bForcePlayer ); + void CalculateTalonTargetLocation(); + + bool CheckReachedTarget(float distance); + void CheckCollisions(float deltaTime); + + void Portalled( idEntity *portal ); + void Show(); + + void SetForcedTarget( bool force ) { bForcedTarget = force; } + void SetForcedPhysicsFactors( float newVel, float newRot, float newPerch ) { velocityFactor = newVel; rotationFactor = newRot; perchRotationFactor = newPerch; } + bool UpdateAnimationControllers(); + + bool FindEnemy( void ); + + void EnterTommyState(); + void EnterFlyState(); + void EnterVehicleState(); + void ExitVehicleState(void); + + void EnterPerchState(); + void EnterAttackState(); + void EndState(); + + void Event_LandAnim(void); + void Event_PreLandAnim(void); + void Event_IdleAnim(void); + void Event_TommyIdleAnim(void); + void Event_FlyAnim(void); + void Event_GlideAnim(void); + void Event_TakeOffAnim(void); + void Event_TakeOffAnimB(void); + + void ReturnToTommy( void ); + + virtual int HasAmmo( ammo_t type, int amount ) { return 0; } + virtual bool UseAmmo( ammo_t type, int amount ) { return false ; } + + bool IsAttacking( void ) { return state == StateAttack; } + +protected: + void Think(); + void FlyMove( void ); + void AdjustFlyingAngles(); + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + + void Damage( idEntity *inflictor, idEntity *attack, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Event_PerchChatter(void); + void Event_PerchSquawk(void); // Perched on a spot and squawking + + void Event_CheckForTarget(); // Checks for nearby targets to land upon + void Event_CheckForEnemy(); // Checks for nearby enemies + + void PerchTicker(void); // Perched on a spot + void FlyTicker(void); // Flying to a perch spot or to Tommy + void TommyTicker(void); // Perched on Tommy's shoulder + void AttackTicker(void); // Attacking an enemy + void VehicleTicker(void); // Owner is in a vehicle, so warp from talon point to talon point + + void StartAttackFX(); + void StopAttackFX(); + +protected: + idEntityPtr owner; + idVec3 velocity; + idVec3 acceleration; + + hhTalonTarget *talonTarget; + idVec3 talonTargetLoc; + idMat3 talonTargetAxis; + + idVec3 lastCheckOrigin; // Used to check if the bird flew through a wall + + float checkTraceTime; // Used to determine when to trace for world collisions + float checkFlyTime; // Used to timeout and find a new flying spot + float flyStraightTime; // Used to force Talon to fly straight for a short time + + bool bLanding; + bool bReturnToTommy; // Forces the bird to return to Tommy, no matter what + + bool bForcedTarget; // True if talon is forced to stay at particular talon target + + bool bClawingAtEnemy; // True if talon is currently playing his attack anim and clawing at creature's heads + + float velocityFactor; + float rotationFactor; + float perchRotationFactor; + + // Anims + int flyAnim; + int glideAnim; + int prelandAnim; + int landAnim; + int idleAnim; + int tommyIdleAnim; + int squawkAnim; + int stepAnim; + int takeOffAnim; + int takeOffAnimB; + int attackAnim; + int preAttackAnim; + + const idDeclSkin *openWingsSkin; + const idDeclSkin *closedWingsSkin; + + // Task variables + idEntityPtr enemy; // Used for attacking + + idEntityPtr trailFx; + + enum States { + StateNone = 0, + StateTommy, + StateFly, + StateVehicle, + StatePerch, + StateAttack + } state; + const idEventDef *stateEvent; // Ticker event for current state +}; + +//----------------------------------------------------------------------------- + +class hhTalonTarget : public idEntity { + friend class hhTalon; + +public: + CLASS_PROTOTYPE(hhTalonTarget); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + ~hhTalonTarget(); + + virtual void Reached( hhTalon *talon ); // Called when Talon reached this point + virtual void Left( hhTalon *talon ); // Called when Talon leaves this point + + static void ShowTargets( void ); + static void HideTargets( void ); + + void SetSquawk( bool newSquawk ) { bShouldSquawk = newSquawk; } + +protected: + void Event_CallTalon( float vel, float rot, float perch ); + void Event_SetPerchState( float newState ); + + void Event_ReleaseTalon(); + + int priority; // priority value. Zero is default, higher numbers is a higher priority + // -1 skips the target spot completely + bool bNotInSpiritWalk; // Talon will skip this target if the player is spiritwalking + + bool bShouldSquawk; // if Talon should squawk on this spot or not + bool bValidForTalon; // if Talon is attacted to this spot or not +}; + +#endif /* __GAME_TALON_H__ */ diff --git a/neo/prey/Prey/game_targetproxy.cpp b/neo/prey/Prey/game_targetproxy.cpp new file mode 100644 index 00000000..6620b6b4 --- /dev/null +++ b/neo/prey/Prey/game_targetproxy.cpp @@ -0,0 +1,282 @@ +// Game_targetproxy.cpp +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhModelProxy +// +// Keeps model/anims in sync with another entity +//========================================================================== +CLASS_DECLARATION(hhAnimatedEntity, hhModelProxy) +END_CLASS + +void hhModelProxy::Spawn() { + renderEntity.noSelfShadow = true; + renderEntity.noShadow = true; + + GetPhysics()->SetContents(0); + + BecomeActive(TH_THINK|TH_TICKER); + + // Required so that models move in place. + GetAnimator()->RemoveOriginOffset( true ); + + original = NULL; +} + +void hhModelProxy::Save(idSaveGame *savefile) const { + original.Save(savefile); + owner.Save(savefile); +} + +void hhModelProxy::Restore( idRestoreGame *savefile ) { + original.Restore(savefile); + owner.Restore(savefile); +} + +void hhModelProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + WriteBindToSnapshot(msg); + + msg.WriteBits(original.GetSpawnId(), 32); + msg.WriteBits(owner.GetSpawnId(), 32); + + msg.WriteBits(renderEntity.allowSurfaceInViewID, GENTITYNUM_BITS); +} + +void hhModelProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + ReadBindFromSnapshot(msg); + + original.SetSpawnId(msg.ReadBits(32)); + owner.SetSpawnId(msg.ReadBits(32)); + + renderEntity.allowSurfaceInViewID = msg.ReadBits(GENTITYNUM_BITS); +} + +void hhModelProxy::ClientPredictionThink( void ) { + Think(); +} + + +/* +=============== +hhModelProxy::SetOriginAndAxis +Note: This was ripped from idEntity::UpdateModelTransform (Was ripped from idEntity) +=============== +*/ +void hhModelProxy::SetOriginAndAxis( idEntity *entity ) { + idVec3 origin; + idMat3 axis; + + + if ( original->GetPhysicsToVisualTransform( origin, axis ) ) { + axis = axis * original->GetPhysics()->GetAxis(); + origin = original->GetPhysics()->GetOrigin() + origin * axis; + } + else { + axis = original->GetPhysics()->GetAxis(); + origin = original->GetPhysics()->GetOrigin(); + } + + SetOrigin( origin ); + SetAxis( axis ); +} + + +void hhModelProxy::SetOriginal(idEntity *other) { + original = other; + if (original.IsValid() && original.GetEntity()) { + SetOriginAndAxis( original.GetEntity() ); + Bind(original.GetEntity(), true); + } +} + +idEntity *hhModelProxy::GetOriginal() { + if (!original.IsValid()) { + return NULL; + } + return original.GetEntity(); +} + +void hhModelProxy::SetOwner(hhPlayer *other) { + owner = other; + renderEntity.allowSurfaceInViewID = owner->entityNumber+1; +} + +hhPlayer *hhModelProxy::GetOwner() { + if (!owner.IsValid()) { + return NULL; + } + return owner.GetEntity(); +} + +void hhModelProxy::Ticker() { + if (!owner.IsValid() || !original.IsValid()) { //rww - added validation for "original" + PostEventMS(&EV_Remove, 0); + return; + } + UpdateVisualState(); +} + +void hhModelProxy::UpdateVisualState() { + + // Update visual state to state of original + if (original.IsValid() && original.GetEntity() && !fl.hidden) { + if (GetRenderEntity()->hModel != original->GetRenderEntity()->hModel) { + const char *modelname; + + if (gameLocal.isMultiplayer && (original->IsType(hhPlayer::Type) || original->IsType(hhSpiritProxy::Type))) { + //rww - players and spirit proxies in mp have variable model names, but keep the original "model" field + //as a fallback. + modelname = original->spawnArgs.GetString( "playerModel" ); + if (!modelname || !modelname[0]) { //empty string, fallback to "model" + modelname = original->spawnArgs.GetString( "model" ); + } + } + else { + modelname = original->spawnArgs.GetString( "model" ); + } + + SetModel(modelname); + } + + // Run our physics to keep our binding to the original up to date. + GetPhysics()->Evaluate( gameLocal.time - gameLocal.previousTime, gameLocal.time ); + + if ( original->GetAnimator() ) { + GetAnimator()->CopyAnimations( *( original->GetAnimator() ) ); + GetAnimator()->CopyPoses( *( original->GetAnimator() ) ); + } + + UpdateVisuals(); + } +} + +void hhModelProxy::ProxyFinished() { + PostEventMS(&EV_Remove, 0); +} + + +//========================================================================== +// +// hhTargetProxy +// +// Special proxy for spirit bow vision that allows sight through walls +//========================================================================== +const idEventDef EV_FadeOutProxy(""); + +CLASS_DECLARATION(hhModelProxy, hhTargetProxy) + EVENT( EV_FadeOutProxy, hhTargetProxy::Event_FadeOut ) +END_CLASS + +void hhTargetProxy::Spawn() { + renderEntity.weaponDepthHack = true; + SetShaderParm(5, 0.5f + ((float)(entityNumber % 8)) / 8.0f); // Unique number [0.5 .. 1.5] for jitter + + owner = NULL; + + //TODO: Need a way to be visible only if owner is local client + + fadeAlpha.Init(gameLocal.time, BOWVISION_FADEIN_DURATION, 0.0f, 1.0f); + StayAlive(); + + fl.networkSync = true; +} + +void hhTargetProxy::Save(idSaveGame *savefile) const { + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); +} + +void hhTargetProxy::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); +} + +void hhTargetProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhModelProxy::WriteToSnapshot(msg); + + msg.WriteFloat(fadeAlpha.GetStartTime()); + msg.WriteFloat(fadeAlpha.GetDuration()); + msg.WriteFloat(fadeAlpha.GetStartValue()); + msg.WriteFloat(fadeAlpha.GetEndValue()); +} + +void hhTargetProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhModelProxy::ReadFromSnapshot(msg); + + fadeAlpha.SetStartTime(msg.ReadFloat()); + fadeAlpha.SetDuration(msg.ReadFloat()); + fadeAlpha.SetStartValue(msg.ReadFloat()); + fadeAlpha.SetEndValue(msg.ReadFloat()); +} + +void hhTargetProxy::ProxyFinished() { + CancelEvents(&EV_FadeOutProxy); + fadeAlpha.Init(gameLocal.time, BOWVISION_FADEOUT_DURATION, 1.0f, 0.0f); + PostEventMS(&EV_Remove, BOWVISION_FADEOUT_DURATION); +} + +void hhTargetProxy::UpdateVisualState() { + //TODO: If owner not in spirit walk, immediately hide and post a remove + // This should handle the case of being in altmode and toggling spiritwalk off + // so that we don't see the targets fade out in non-spiritwalk mode + + //TODO: net play support: snapshot owner->entityNumber. Hide if owner isn't the localclient +// if (owner->entityNumber == gameLocal.localClientNum) { +// } + + + if( !original.IsValid() || !original.GetEntity() || original->IsHidden() ) { // mdl: Don't show hidden entities (keeps actors from showing up in bowvision after gibbing) + //rww - added validity checks for "original" + Hide(); + return; + } + + // Check to see if within players view + idVec3 toProxy = GetPhysics()->GetOrigin() - owner->GetPhysics()->GetOrigin(); + float score = toProxy * (owner->viewAngles.ToMat3()[0]); + if (score < 0.2f) { + Hide(); + } + else { + Show(); + SetShaderParm(3, fadeAlpha.GetCurrentValue(gameLocal.time)); + hhModelProxy::UpdateVisualState(); + } +} + +void hhTargetProxy::StayAlive() { + // Stay alive for BOWVISION_UPDATE_FREQUENCY longer (MS) + a little slop + CancelEvents(&EV_Remove); + CancelEvents(&EV_FadeOutProxy); + PostEventMS(&EV_FadeOutProxy, BOWVISION_UPDATE_FREQUENCY * 2); + + // Fade back in if fading out, otherwise stay faded in + float curvalue = fadeAlpha.GetCurrentValue(gameLocal.time); + fadeAlpha.Init(gameLocal.time, BOWVISION_FADEIN_DURATION, curvalue, 1.0f); + + if ( owner.IsValid() && owner.GetEntity() ) { + UpdateVisualState(); + } +} + +void hhTargetProxy::Event_FadeOut() { + ProxyFinished(); +} diff --git a/neo/prey/Prey/game_targetproxy.h b/neo/prey/Prey/game_targetproxy.h new file mode 100644 index 00000000..d82d62de --- /dev/null +++ b/neo/prey/Prey/game_targetproxy.h @@ -0,0 +1,63 @@ + +#ifndef __GAME_TARGETPROXY_H__ +#define __GAME_TARGETPROXY_H__ + +class hhModelProxy : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhModelProxy ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - net code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void SetOriginal(idEntity *other); + idEntity * GetOriginal(); + virtual void ProxyFinished(); + virtual void Ticker(); + virtual void UpdateVisualState(); + virtual void SetOriginAndAxis( idEntity * ); + void SetOwner(hhPlayer *other); + hhPlayer * GetOwner(); + +protected: + //rww - these need to be entityptr's, not raw pointers + idEntityPtr original; + idEntityPtr owner; +}; + + +#define BOWVISION_FADEIN_DURATION 1500 +#define BOWVISION_FADEOUT_DURATION 1500 +#define BOWVISION_UPDATE_FREQUENCY 12*MAX_GENTITIES/BOWVISION_ROVER_STRIDE // Rate at which to search for proxies +#define BOWVISION_ROVER_STRIDE 25 // Number of entities to check each tick + +class hhTargetProxy : public hhModelProxy { +public: + CLASS_PROTOTYPE( hhTargetProxy ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - net code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void StayAlive(); + virtual void ProxyFinished(); + virtual void UpdateVisualState(); + +protected: + void Event_FadeOut(); + +protected: + idInterpolate fadeAlpha; +}; + + +#endif diff --git a/neo/prey/Prey/game_targets.cpp b/neo/prey/Prey/game_targets.cpp new file mode 100644 index 00000000..6260cb4f --- /dev/null +++ b/neo/prey/Prey/game_targets.cpp @@ -0,0 +1,849 @@ +/* + game_targets.cpp + + These entities, when targeted, perform an action + +*/ + + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_Autosave( "" ); +const idEventDef EV_FinishedSave( "" ); + +/*********************************************************************** + + hhTarget_SetSkin + + Set all targets to given skin +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_SetSkin ) + EVENT( EV_Activate, hhTarget_SetSkin::Event_Trigger ) +END_CLASS + +/* +================ +hhTarget_SetSkin::Spawn +================ +*/ +void hhTarget_SetSkin::Spawn( void ) { + spawnArgs.GetString( "skinName", "", skinName ); +} + + +/* +================ + hhTarget_SetSkin::Event_Trigger +================ +*/ +void hhTarget_SetSkin::Event_Trigger( idEntity *activator ) { + idEntity *ent = NULL; + + // Set all targets to the specified skin + for( int t=0; tSetSkinByName( (ent->GetSkin()) ? NULL : skinName.c_str() ); + } +} + + + +/*********************************************************************** + + hhTarget_Enable + + Enable all targets +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_Enable ) + EVENT( EV_Activate, hhTarget_Enable::Event_Trigger ) +END_CLASS + +/* +================ +hhTarget_Enable::Spawn +================ +*/ +void hhTarget_Enable::Spawn( void ) { +} + + +/* +================ + hhTarget_Enable::Event_Trigger +================ +*/ +void hhTarget_Enable::Event_Trigger( idEntity *activator ) { + + // Enable all targets + for (int t=0; tPostEventMS( &EV_Enable, 0 ); + } + } +} + + +/*********************************************************************** + + hhTarget_Disable + + Disable all targets +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_Disable ) + EVENT( EV_Activate, hhTarget_Disable::Event_Trigger ) +END_CLASS + +/* +================ +hhTarget_Disable::Spawn +================ +*/ +void hhTarget_Disable::Spawn( void ) { +} + + +/* +================ + hhTarget_Disable::Event_Trigger +================ +*/ +void hhTarget_Disable::Event_Trigger( idEntity *activator ) { + + // Disable all targets + for (int t=0; tPostEventMS( &EV_Disable, 0 ); + } + } +} + + +/*********************************************************************** + + hhTarget_Earthquake + +***********************************************************************/ +const idEventDef EV_TurnOff( "", NULL ); + +CLASS_DECLARATION( idTarget, hhTarget_Earthquake ) + EVENT( EV_Activate, hhTarget_Earthquake::Event_Trigger ) + EVENT( EV_TurnOff, hhTarget_Earthquake::Event_TurnOff ) +END_CLASS + +/* +================ +hhTarget_Earthquake::Spawn +================ +*/ +void hhTarget_Earthquake::Spawn( void ) { + //PDMMERGE: Modify to use the sound for the visual shake exclusively + // and forcefield for the world shake exclusively. + //This duplicates functionality of func_earthquake. Check their effect + //out and see which we want to keep. If keeping, this could then become func_earthquake. + //Could alternatively use a sound player and a func_idforcefield + + shakeTime = SEC2MS(spawnArgs.GetFloat("shake_time")); + shakeAmplitude = spawnArgs.GetFloat("shake_severity"); + + forceField.RandomTorque( shakeAmplitude * 40); + + // Grab clip model to use for checking who's currently encroaching me + cm = new idClipModel( GetPhysics()->GetClipModel() ); + if( !cm ) { + gameLocal.Error( "hhTarget_Earthquake::Spawn: Unable to spawn idClipModel\n" ); + return; + } + forceField.SetClipModel( cm ); + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); +} + +void hhTarget_Earthquake::Save(idSaveGame *savefile) const { + savefile->WriteFloat( shakeTime ); + savefile->WriteFloat( shakeAmplitude ); + savefile->WriteStaticObject( forceField ); + savefile->WriteClipModel( cm ); +} + +void hhTarget_Earthquake::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( shakeTime ); + savefile->ReadFloat( shakeAmplitude ); + savefile->ReadStaticObject( forceField ); + savefile->ReadClipModel( cm ); +} + +/* +================ + hhTarget_Earthquake::Think +================ +*/ +void hhTarget_Earthquake::Think() { + if (thinkFlags & TH_THINK) { + forceField.Evaluate( gameLocal.time ); + } +} + + +/* +================ +hhTarget_Earthquake::Event_TurnOff +================ +*/ +void hhTarget_Earthquake::Event_TurnOff( void ) { + hhPlayer *player; + BecomeInactive(TH_THINK); + StopSound(SND_CHANNEL_BODY); + + // Turn camera interpolator back on + for (int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast(gameLocal.entities[ i ]); + if ( player ) { + player->cameraInterpolator.SetInterpolationType( IT_VariableMidPointSinusoidal ); + } + } +} + +/* +================ + hhTarget_Earthquake::Event_Trigger +================ +*/ +void hhTarget_Earthquake::Event_Trigger( idEntity *activator ) { + hhPlayer *player; + + if( !cm ) { // No collision model + return; + } + + StartSound("snd_quake", SND_CHANNEL_BODY); + + idBounds bounds; + bounds.FromTransformedBounds( cm->GetBounds(), cm->GetOrigin(), cm->GetAxis() ); + //gameRenderWorld->DebugBounds(colorRed, bounds, vec3_origin, 5000); + + for (int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast(gameLocal.entities[ i ]); + if ( !player || ( player->fl.notarget ) ) { + continue; + } + + if( bounds.IntersectsBounds(player->GetPhysics()->GetAbsBounds()) ) { + player->cameraInterpolator.SetInterpolationType(IT_None); + } + } + + // Turn on physics + BecomeActive( TH_THINK ); + PostEventSec( &EV_TurnOff, MS2SEC(shakeTime) ); +} + + +/*********************************************************************** + + hhTarget_SetLightParm + +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_SetLightParm ) + EVENT( EV_Activate, hhTarget_SetLightParm::Event_Activate ) +END_CLASS + +/* +================ +hhTarget_SetLightParm::Event_Activate +================ +*/ +void hhTarget_SetLightParm::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->IsType(idLight::Type) ) { + static_cast(ent)->SetLightParm( parmnum, value ); + } + } + if (spawnArgs.GetBool("toggle") && (value == 0 || value == 1)) { + int val = value; + val ^= 1; + value = val; + spawnArgs.SetFloat(va("shaderParm%d", parmnum), value); + } + } + } +} + +/*********************************************************************** + + hhTarget_PlayWeaponAnim + +***********************************************************************/ + +CLASS_DECLARATION( idTarget, hhTarget_PlayWeaponAnim ) + EVENT( EV_Activate, hhTarget_PlayWeaponAnim::Event_Trigger ) +END_CLASS + +/* +================ + hhTarget_PlayWeaponAnim::Event_Trigger +================ +*/ +void hhTarget_PlayWeaponAnim::Event_Trigger( idEntity *activator ) { + hhPlayer* pPlayer = NULL; + + if( activator && activator->IsType(hhPlayer::Type) ) { + pPlayer = static_cast( activator ); + if( pPlayer ) { + pPlayer->ProcessEvent( &EV_PlayWeaponAnim, spawnArgs.GetString("anim", "initialPickup"), spawnArgs.GetInt("numTries") ); + } + } +} + + +//========================================================================== +// +// hhTarget_ControlVehicle +// +//========================================================================== + +CLASS_DECLARATION(idTarget, hhTarget_ControlVehicle) + EVENT( EV_Activate, hhTarget_ControlVehicle::Event_Activate ) +END_CLASS + + +void hhTarget_ControlVehicle::Spawn() { +} + +void hhTarget_ControlVehicle::Event_Activate( idEntity *activator ) { + hhVehicle *vehicle = NULL; + + if (activator->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(activator); + + // Search target list to find vehicle + int numTargets = targets.Num(); + for (int ix=0; ixIsType(hhVehicle::Type)) { + vehicle = static_cast(targets[ix].GetEntity()); + } + } + } + + if( vehicle ) { + player->EnterVehicle( vehicle ); + } + } +} + + +//========================================================================== +// +// hhTarget_AttachToRail +// +// OBSOLETE, just target the hhRailRide directly +//========================================================================== + +CLASS_DECLARATION(idTarget, hhTarget_AttachToRail) + EVENT( EV_Activate, hhTarget_AttachToRail::Event_Activate ) +END_CLASS + + +void hhTarget_AttachToRail::Spawn() { +} + +// Bind player to given bone of a targeted hhAnimator +void hhTarget_AttachToRail::Event_Activate( idEntity *activator ) { + hhRailRide *rail = NULL; + + if (activator && activator->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(activator); + + // Search target list to find hhRailController + int numTargets = targets.Num(); + for (int ix=0; ixIsType(hhRailRide::Type)) { + rail = static_cast(targets[ix].GetEntity()); + } + } + } + + if (rail) { + rail->Attach(player, false); + } + } +} + +//========================================================================== +// +// hhTarget_EnableReactions +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_EnableReactions ) + EVENT( EV_Activate, hhTarget_EnableReactions::Event_Activate ) +END_CLASS + +void hhTarget_EnableReactions::Spawn() { +} + +void hhTarget_EnableReactions::Event_Activate(idEntity *activator) { + // Enable reactions on all targets + for (int t=0; tfl.refreshReactions = true; + } + } +} + +//========================================================================== +// +// hhTarget_DisableReactions +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_DisableReactions ) + EVENT( EV_Activate, hhTarget_DisableReactions::Event_Activate ) +END_CLASS + +void hhTarget_DisableReactions::Spawn() { +} + +void hhTarget_DisableReactions::Event_Activate(idEntity *activator) { + // Disable reactions on all targets + for (int t=0; tfl.refreshReactions = false; + } + } +} + +//========================================================================== +// +// hhTarget_EnablePassageway +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_EnablePassageway ) + EVENT( EV_Activate, hhTarget_EnablePassageway::Event_Activate ) +END_CLASS + +void hhTarget_EnablePassageway::Spawn() { +} + +void hhTarget_EnablePassageway::Event_Activate(idEntity *activator) { + // Enable reactions on all targets + for (int t=0; tIsType(hhAIPassageway::Type) ) { + static_cast(ent)->SetEnablePassageway(TRUE); + } + } +} + +//========================================================================== +// +// hhTarget_DisablePassageway +// +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_DisablePassageway ) + EVENT( EV_Activate, hhTarget_DisablePassageway::Event_Activate ) +END_CLASS + +void hhTarget_DisablePassageway::Spawn() { +} + +void hhTarget_DisablePassageway::Event_Activate(idEntity *activator) { + // Enable reactions on all targets + for (int t=0; tIsType(hhAIPassageway::Type)) { + static_cast(ent)->SetEnablePassageway(FALSE); + } + } +} + + +const idEventDef EV_TriggerTargets( "" ); + +//========================================================================== +// +// hhTarget_PatternRelay +// +// When triggered, relays trigger messages in a pattern +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_PatternRelay ) + EVENT( EV_Activate, hhTarget_PatternRelay::Event_Activate ) + EVENT( EV_TriggerTargets, hhTarget_PatternRelay::Event_TriggerTargets ) +END_CLASS + +void hhTarget_PatternRelay::Spawn() { + timeGranularity = spawnArgs.GetFloat("timestep", "1"); +} + +void hhTarget_PatternRelay::Event_Activate(idEntity *activator) { + idStr pattern = spawnArgs.GetString("pattern", "xoxoxo"); + + //TODO: Clear out any from last time around? + + float issueTime = 0.0f; + int length = pattern.Length(); + for (int ix=0; ixhud->SetStateInt("subtitlex", bCentered ? 0 : x); + player->hud->SetStateInt("subtitley", y); + player->hud->SetStateInt("subtitlecentered", bCentered); + player->hud->SetStateString("subtitletext", text); + player->hud->StateChanged(gameLocal.time); + player->hud->HandleNamedEvent("DisplaySubtitle"); + + CancelEvents(&EV_TurnOff); + PostEventSec(&EV_TurnOff, duration); +} + +void hhTarget_Subtitle::Event_FadeOutText() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->hud) { + player->hud->HandleNamedEvent("RemoveSubtitle"); + } +} + + +//========================================================================== +// +// hhTarget_EndLevel +// +// When triggered, does a level transition +//========================================================================== +CLASS_DECLARATION( idTarget_EndLevel, hhTarget_EndLevel ) + EVENT( EV_Activate, hhTarget_EndLevel::Event_Activate ) +END_CLASS + +void hhTarget_EndLevel::Event_Activate(idEntity *activator) { + idUserInterface *guiLoading = NULL; + idStr nextMap; + if ( spawnArgs.GetString( "nextMap", "", nextMap ) ) { + + // Session code hasn't yet loaded the gui for next level here, so we preload it based on the same + // logic as it uses in idSessionLocal::LoadLoadingGui(). + idStr stripped = nextMap; + stripped.StripFileExtension(); + stripped.StripPath(); + idStr guiMap = va( "guis/map/%s.gui", stripped.c_str() ); + 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); + guiLoading->SetStateBool("showddainfo", true); + + const idDecl *mapDecl = declManager->FindType(DECL_MAPDEF, stripped.c_str(), false ); + if ( mapDecl ) { + const idDeclEntityDef *mapInfo = static_cast(mapDecl); + const char *friendlyName = mapInfo->dict.GetString("name"); + guiLoading->SetStateString("friendlyname", friendlyName); + } + + guiLoading->StateChanged(gameLocal.time); + + // HUMANHEAD CJR: If the player hits this and they are spiritwalking, stop the spiritwalk before loading the next level + if ( activator->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( activator ); + if ( player ) { + + // Player managed kill themselves at the end of level? Clear deathwalk flags + if (player->IsDeathWalking()) { + player->bDeathWalk = false; + player->SetHealth( 50.0f ); // Don't say I never got you anything + player->SetSpiritPower( 0.0f ); // Clear spirit power + } + + if ( player->IsSpiritWalking() ) { + player->PutawayEtherealWeapon(); // Don't do a full spiritstop, as that teleports the player, too. Just fix the player's weapon + } + } + } + // END CJR + } + + idTarget_EndLevel::Event_Activate(activator); +} + + +//========================================================================== +// +// hhTarget_ConsolidatePlayers +// +// When triggered, consolidates all players into a single view (coop) +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_ConsolidatePlayers ) + EVENT( EV_Activate, hhTarget_ConsolidatePlayers::Event_Activate ) +END_CLASS + +void hhTarget_ConsolidatePlayers::Event_Activate(idEntity *activator) { + if (gameLocal.IsCooperative()) { + //TODO: Implement + } +} + + +//========================================================================== +// +// hhTarget_WarpPlayers +// +// When triggered, teleports all players to targeted start spots (coop) +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_WarpPlayers ) + EVENT( EV_Activate, hhTarget_WarpPlayers::Event_Activate ) +END_CLASS + +void hhTarget_WarpPlayers::Event_Activate(idEntity *activator) { + if (gameLocal.IsCooperative()) { + //TODO: Implement + } +} + +//========================================================================== +// +// hhTarget_FollowPath +// +// When triggered, make an ai follow a path +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_FollowPath ) + EVENT( EV_Activate, hhTarget_FollowPath::Event_Activate ) +END_CLASS + +void hhTarget_FollowPath::Event_Activate(idEntity *activator) { + int i; + idEntity *ent; + const function_t *func; + const char *funcName; + idThread *thread; + hhMonsterAI *entAI; + + 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() ); + } + entAI = static_cast(ent); + if ( entAI && entAI->AI_FOLLOWING_PATH ) { + gameLocal.Warning( "hhTarget_FollowPath %s already following path", ent->name.c_str() ); + continue; + } + ent->spawnArgs.Set( "alt_path", spawnArgs.GetString( "alt_path" ) ); + // create a thread and call the function + thread = new idThread(); + thread->CallFunction( ent, func, true ); + thread->Start(); + } + } +} + +//=============================================================================== +// +// hhTarget_LockDoor +// +//=============================================================================== + +CLASS_DECLARATION( idTarget, hhTarget_LockDoor ) + EVENT( EV_Activate, hhTarget_LockDoor::Event_Activate ) +END_CLASS + +void hhTarget_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(); + if ( ent && ent->IsType( idDoor::Type ) ) { + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + if ( ent && ent->IsType( hhModelDoor::Type ) ) { + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + if ( ent && ent->IsType( hhProxDoor::Type ) ) { + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + } +} + +//========================================================================== +// +// hhTarget_DisplayGui +// +// When triggered, displays full screen gui +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_DisplayGui ) + EVENT( EV_Activate, hhTarget_DisplayGui::Event_Activate ) +END_CLASS + +void hhTarget_DisplayGui::Event_Activate(idEntity *activator) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->IsType(hhPlayer::Type)) { + static_cast(player)->SetOverlayGui( spawnArgs.GetString("gui_overlay") ); + } +} + + +//========================================================================== +// +// hhTarget_Autosave +// +// When triggered, autosaves +//========================================================================== +CLASS_DECLARATION( idTarget, hhTarget_Autosave ) + EVENT( EV_Activate, hhTarget_Autosave::Event_Activate ) +END_CLASS + +void hhTarget_Autosave::Event_Activate(idEntity *activator) { + const char *desc = spawnArgs.GetString( "text_savename" ); + if ( !desc || desc[0] == 0 ) { + gameLocal.Warning( "hhTarget_Autosave has no text_savename key" ); + desc = ""; + } + + idStr saveName = va( common->GetLanguageDict()->GetString( "#str_00838" ), desc ); + //HUMANHEAD PCF mdl 05/05/06 - Changed ' to " to avoid problems when saves contained ' + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "savegame \"%s\"", saveName.c_str() ) ); +} + + +//========================================================================== +// +// hhTarget_Show +// +//========================================================================== + +CLASS_DECLARATION( idTarget, hhTarget_Show ) + EVENT( EV_Activate, hhTarget_Show::Event_Activate ) +END_CLASS + +void hhTarget_Show::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Show(); + } + } +} + +//========================================================================== +// +// hhTarget_Hide +// +//========================================================================== + +CLASS_DECLARATION( idTarget, hhTarget_Hide ) + EVENT( EV_Activate, hhTarget_Hide::Event_Activate ) +END_CLASS + +void hhTarget_Hide::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Hide(); + } + } +} + diff --git a/neo/prey/Prey/game_targets.h b/neo/prey/Prey/game_targets.h new file mode 100644 index 00000000..0553bea5 --- /dev/null +++ b/neo/prey/Prey/game_targets.h @@ -0,0 +1,235 @@ + +#ifndef __GAME_TARGETS_H__ +#define __GAME_TARGETS_H__ + +class hhTarget_SetSkin : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_SetSkin); + + void Spawn(void); + void Save( idSaveGame *savefile ) const { savefile->WriteString(skinName); } + void Restore( idRestoreGame *savefile ) { savefile->ReadString(skinName); } + +protected: + void Event_Trigger( idEntity *activator ); + + idStr skinName; +}; + +class hhTarget_Enable : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Enable); + + void Spawn(void); + +protected: + void Event_Trigger( idEntity *activator ); +}; + +class hhTarget_Disable : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Disable); + + void Spawn(void); + +protected: + void Event_Trigger( idEntity *activator ); +}; + +class hhTarget_Earthquake : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Earthquake); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Present(void) { } + virtual void Think(void); + +protected: + void Event_Trigger( idEntity *activator ); + void Event_TurnOff( void ); + +private: + float shakeTime; + float shakeAmplitude; + idForce_Field forceField; + idClipModel *cm; +}; + +class hhTarget_SetLightParm : public idTarget { +public: + CLASS_PROTOTYPE( hhTarget_SetLightParm ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_PlayWeaponAnim : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_PlayWeaponAnim); + +protected: + void Event_Trigger( idEntity *activator ); +}; + +class hhTarget_ControlVehicle : public idTarget { + CLASS_PROTOTYPE( hhTarget_ControlVehicle ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_AttachToRail : public idTarget { + CLASS_PROTOTYPE( hhTarget_AttachToRail ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_EnableReactions : public idTarget { + CLASS_PROTOTYPE( hhTarget_EnableReactions ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_DisableReactions : public idTarget { + CLASS_PROTOTYPE( hhTarget_DisableReactions ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_EnablePassageway : public idTarget { + CLASS_PROTOTYPE( hhTarget_EnablePassageway ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_DisablePassageway : public idTarget { + CLASS_PROTOTYPE( hhTarget_DisablePassageway ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_PatternRelay : public idTarget { + CLASS_PROTOTYPE( hhTarget_PatternRelay ); + +public: + void Spawn(); + void Save( idSaveGame *savefile ) const { savefile->WriteFloat(timeGranularity); } + void Restore( idRestoreGame *savefile ) { savefile->ReadFloat(timeGranularity); } + +protected: + void Event_Activate( idEntity *activator ); + void Event_TriggerTargets(); + + float timeGranularity; +}; + +class hhTarget_Subtitle : public idTarget { + CLASS_PROTOTYPE( hhTarget_Subtitle ); + +public: + void Spawn(); + +protected: + void Event_Activate( idEntity *activator ); + void Event_FadeOutText(); +}; + + +class hhTarget_EndLevel : public idTarget_EndLevel { + CLASS_PROTOTYPE( hhTarget_EndLevel ); + +protected: + virtual void Event_Activate(idEntity *activator); +}; + + +class hhTarget_ConsolidatePlayers : public idTarget { + CLASS_PROTOTYPE( hhTarget_ConsolidatePlayers ); + +protected: + void Event_Activate( idEntity *activator ); +}; + + +class hhTarget_WarpPlayers : public idTarget { + CLASS_PROTOTYPE( hhTarget_WarpPlayers ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_FollowPath : public idTarget { + CLASS_PROTOTYPE( hhTarget_FollowPath ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_LockDoor: public idTarget { +public: + CLASS_PROTOTYPE( hhTarget_LockDoor ); + +private: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_DisplayGui : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_DisplayGui); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_Autosave : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Autosave); + +protected: + void Event_Activate( idEntity *activator ); + void Event_Autosave( void ); + void Event_FinishedSave( void ); +}; + +class hhTarget_Show : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Show); + +protected: + void Event_Activate( idEntity *activator ); +}; + +class hhTarget_Hide : public idTarget { +public: + CLASS_PROTOTYPE(hhTarget_Hide); + +protected: + void Event_Activate( idEntity *activator ); +}; + +#endif /* __GAME_TARGETS_H__ */ diff --git a/neo/prey/Prey/game_trackmover.cpp b/neo/prey/Prey/game_trackmover.cpp new file mode 100644 index 00000000..6bcbb3ff --- /dev/null +++ b/neo/prey/Prey/game_trackmover.cpp @@ -0,0 +1,172 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_StartNextMove( "", NULL ); +const idEventDef EV_Kill( "" ); + +CLASS_DECLARATION(idMover, hhTrackMover) + EVENT( EV_PostSpawn, hhTrackMover::Event_PostSpawn ) + EVENT( EV_StartNextMove, hhTrackMover::Event_StartNextMove ) + EVENT( EV_Activate, hhTrackMover::Event_Activate ) + EVENT( EV_Deactivate, hhTrackMover::Event_Deactivate ) + EVENT( EV_Kill, hhTrackMover::Event_Kill ) +END_CLASS + +hhTrackMover::hhTrackMover() { + currentNode = NULL; +} + +void hhTrackMover::Spawn( void ) { + timeBetweenMoves = spawnArgs.GetInt("moveDelayMS"); + Hide(); + GetPhysics()->SetContents( 0 ); + fl.takedamage = false; + PostEventMS(&EV_PostSpawn, 0); +} + +// Return the next hhTrackNode to move to based on current player position +idEntity *hhTrackMover::GetNextDestination( void ) { + + idEntity *node, *minNode; + float distToPlayerSquared, minDistToPlayerSquared; + + if ( !currentNode ) { + return NULL; + } + + // Get player closest to currentNode + hhPlayer *player = NULL; + float bestDistSqr = idMath::INFINITY; + idVec3 origin = currentNode->GetPhysics()->GetOrigin(); + for ( int i = 0; i < MAX_CLIENTS ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if (ent && ent->IsType(hhPlayer::Type)) { + idVec3 toEnt = ent->GetPhysics()->GetOrigin() - origin; + float distSqr = toEnt.LengthSqr(); + if (distSqr < bestDistSqr) { + bestDistSqr = distSqr; + player = static_cast(ent); + } + } + } + if ( !player ) { + return NULL; + } + + // Find closest adjacent node to player + minNode = currentNode; + minDistToPlayerSquared = (player->GetPhysics()->GetOrigin() - currentNode->GetPhysics()->GetOrigin()).LengthSqr(); + for (int t=0; ttargets.Num(); t++) { + node = currentNode->targets[t].GetEntity(); + if (node) { + idVec3 toPlayer = player->GetPhysics()->GetOrigin() - node->GetPhysics()->GetOrigin(); + distToPlayerSquared = toPlayer.LengthSqr(); + if (distToPlayerSquared < minDistToPlayerSquared) { + minDistToPlayerSquared = distToPlayerSquared; + minNode = node; + + if(p_mountedGunDebug.GetInteger()) { + gameRenderWorld->DebugLine(colorGreen, currentNode->GetPhysics()->GetOrigin(), node->GetPhysics()->GetOrigin(), 2000, false); + } + } + else { + if(p_mountedGunDebug.GetInteger()) { + gameRenderWorld->DebugLine(colorYellow, currentNode->GetPhysics()->GetOrigin(), node->GetPhysics()->GetOrigin(), 2000, false); + } + } + } + } + + return minNode; +} + +void hhTrackMover::DoneMoving( void ) { + if( state == StateGlobal ) { + idMover::DoneMoving(); + PostEventMS( &EV_StartNextMove, 0); + } +} + +void hhTrackMover::Event_PostSpawn() { + bool activate; + spawnArgs.GetBool("autoactivate", "0", activate); + + if( !targets.Num() || !targets[0].IsValid() ) { + gameLocal.Error("hhTrackMover %s doesn't have a target tracknode\n", name.c_str()); + } + + currentNode = targets[0].GetEntity(); + dest_position = GetLocalCoordinates(currentNode->GetPhysics()->GetOrigin()); + SetOrigin( dest_position ); + + if (activate) { + state = StateGlobal; + PostEventMS( &EV_StartNextMove, 1000 ); + } else { + state = StateIdle; + } +} + +void hhTrackMover::Event_Activate( idEntity *activator ) { + if( state == StateIdle ) { + PostEventMS( &EV_StartNextMove, 0 ); + state = StateGlobal; + } +} + +void hhTrackMover::Event_Deactivate() { + if( state == StateGlobal ) { + state = StateIdle; + + DoneMoving(); + DoneRotating(); + CancelEvents(&EV_StartNextMove); + + } +} + +void hhTrackMover::Event_StartNextMove() { + idAngles Angles; + idVec3 Origin; + idEntity *ent = GetNextDestination(); + + if (ent && ent != currentNode) { + + Origin = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + if( !GetPhysics()->GetOrigin().Compare(Origin) ) { + dest_position = Origin; + BeginMove( NULL ); + } + + Angles = ent->GetPhysics()->GetAxis().ToAngles(); + if( !GetPhysics()->GetAxis().ToAngles().Compare(Angles) ) { + dest_angles = Angles; + BeginRotation( NULL, true ); + } + + currentNode = ent; + } else { + PostEventMS( &EV_StartNextMove, timeBetweenMoves ); + } +} + +void hhTrackMover::Event_Kill() { + idMover::DoneMoving(); + idMover::DoneRotating(); + CancelEvents(&EV_StartNextMove); + state = StateDead; +} + +void hhTrackMover::Save( idSaveGame *savefile ) const { + savefile->WriteInt( state ); + savefile->WriteInt( timeBetweenMoves ); + savefile->WriteObject( currentNode ); +} + +void hhTrackMover::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( reinterpret_cast ( state ) ); + savefile->ReadInt( timeBetweenMoves ); + savefile->ReadObject( reinterpret_cast ( currentNode ) ); +} diff --git a/neo/prey/Prey/game_trackmover.h b/neo/prey/Prey/game_trackmover.h new file mode 100644 index 00000000..606434fe --- /dev/null +++ b/neo/prey/Prey/game_trackmover.h @@ -0,0 +1,33 @@ +#ifndef __GAME_TRACKMOVER_H__ +#define __GAME_TRACKMOVER_H__ + + +class hhTrackMover : public idMover { + CLASS_PROTOTYPE( hhTrackMover ); +public: + hhTrackMover(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + idEntity * GetNextDestination( void ); + void DoneMoving( void ); + +protected: + enum States { + StateGlobal = 0, + StateIdle, + StateDead + } state; + + int timeBetweenMoves; + idEntity * currentNode; // Current node of track + + void Event_Activate( idEntity *activator ); + void Event_Deactivate(); + void Event_PostSpawn( void ); + void Event_StartNextMove( void ); + void Event_Kill(); +}; + + +#endif /* __GAME_TRACKMOVER_H__ */ diff --git a/neo/prey/Prey/game_trigger.cpp b/neo/prey/Prey/game_trigger.cpp new file mode 100644 index 00000000..88d96e01 --- /dev/null +++ b/neo/prey/Prey/game_trigger.cpp @@ -0,0 +1,1482 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/********************************************************************** + +hhFuncParmAccessor + +**********************************************************************/ + +CLASS_DECLARATION( idClass, hhFuncParmAccessor ) +END_CLASS + +/* +================ +hhFuncParmAccessor::hhFuncParmAccessor +================ +*/ +hhFuncParmAccessor::hhFuncParmAccessor() { + function = NULL; +} + +/* +================ +hhFuncParmAccessor::hhFuncParmAccessor +================ +*/ +hhFuncParmAccessor::hhFuncParmAccessor( const hhFuncParmAccessor* accessor ) { + assert( accessor ); + + SetInfo( accessor->GetReturnKey(), accessor->GetFunction(), accessor->GetParms() ); +} + +/* +================ +hhFuncParmAccessor::hhFuncParmAccessor +================ +*/ +hhFuncParmAccessor::hhFuncParmAccessor( const hhFuncParmAccessor& accessor ) { + SetInfo( accessor.GetReturnKey(), accessor.GetFunction(), accessor.GetParms() ); +} + +/* +================ +hhFuncParmAccessor::SetInfo +================ +*/ +void hhFuncParmAccessor::SetInfo( const char* returnKey, const function_t* func, const idList& parms ) { + SetFunction( func ); + SetParms( parms ); + SetReturnKey( returnKey ); + + Verify(); +} + +void hhFuncParmAccessor::InsertParm_String( const char *text, int index ) { + InsertParm( text, index ); +} + +void hhFuncParmAccessor::InsertParm_Float( float value, int index ) { + InsertParm( va("%.2f", value), index ); +} + +void hhFuncParmAccessor::InsertParm_Int( int value, int index ) { + InsertParm( va("%d", value), index ); +} + +void hhFuncParmAccessor::InsertParm_Vector( const idVec3 &vec, int index ) { + InsertParm( va("'%.2f %.2f %.2f'", vec[0], vec[1], vec[2]), index ); +} + +void hhFuncParmAccessor::InsertParm_Entity( const idEntity *ent, int index ) { + if ( ent ) { + InsertParm( ent->GetName(), index ); + } +} + +void hhFuncParmAccessor::SetParm_Entity( const idEntity *ent, int index ) { + if ( ent ) { + parms.AssureSize(index+1); + parms[index] = ent->GetName(); + } +} + +void hhFuncParmAccessor::SetParm_String( const char *str, int index ) { + if ( str ) { + parms.AssureSize(index+1); + parms[index] = str; + } +} + +void hhFuncParmAccessor::InsertParm( const char *text, int index ) { + parms.Insert( text, index ); +} + +/* +================ +hhFuncParmAccessor::ParseFunctionKeyValue +================ +*/ +void hhFuncParmAccessor::ParseFunctionKeyValue( const char* value ) { + if( !value || !value[0] ) { + return; + } + + hhUtils::SplitString( idStr(value), GetParms(), ' ' ); + ParseFunctionKeyValue( GetParms() ); +} + +/* +================ +hhFuncParmAccessor::ParseFunctionKeyValue +================ +*/ +void hhFuncParmAccessor::ParseFunctionKeyValue( idList& valueList ) { + SetFunction( FindFunction(valueList[0]) ); + if( GetFunction() ) { + valueList.RemoveIndex( 0 );//Remove function name + } else { + gameLocal.Warning("hhFuncParmAccessor: Function %s did not exist", (const char*)valueList[0]); + SetReturnKey( valueList[0] ); + valueList.RemoveIndex( 0 );//Remove return key + SetFunction( FindFunction(valueList[0]) ); + valueList.RemoveIndex( 0 );//Remove function name + } +} + +/* +================ +hhFuncParmAccessor::FindFunction +================ +*/ +function_t* hhFuncParmAccessor::FindFunction( const char* funcname ) { + function_t* func = NULL; + + if( funcname && *funcname ) { + func = gameLocal.program.FindFunction( funcname ); + } + + return func; +} + +/* +================ +hhFuncParmAccessor::CallFunction +================ +*/ +void hhFuncParmAccessor::CallFunction( idDict& returnDict ) { + if( !GetFunction() ) { + return; + } + + hhThread* scriptThread = new hhThread; + if( !scriptThread ) { + return; + } + + scriptThread->ClearStack(); + if( !scriptThread->ParseAndPushArgsOntoStack(GetParms(), GetFunction()) ) { + SAFE_DELETE_PTR( scriptThread ); //This is needed because we won't be removed by Execute + return; + } + + scriptThread->CallFunction( GetFunction(), false ); + + scriptThread->Execute(); + + idTypeDef* returnType = GetReturnType(); + if( !GetReturnKey() || !GetReturnKey()[0] ) { + return; + } + + if( returnType == &type_void ) { + gameLocal.Warning( "Return key assigned for function (%s) that returns void\n", GetFunctionName() ); + return; + } + + returnDict.Set( GetReturnKey(), returnType->GetReturnValueAsString(gameLocal.program) ); +} + +/* +================ +hhFuncParmAccessor::Verify +================ +*/ +void hhFuncParmAccessor::Verify() { + if( !GetFunction() ) { + return; + } + + int numParms = GetFunction()->def->TypeDef()->NumParameters(); + const char* parm = NULL; + idTypeDef* parmType = NULL; + + if( numParms != GetParms().Num() ) { + gameLocal.Warning( "Wrong # of parms passed for function %s", GetFunctionName() ); + return; + } + + if( GetReturnType() == &type_void && GetReturnKey() && GetReturnKey()[0] ) { + gameLocal.Warning( "Used Return key %s for function %s that returns void", GetReturnKey(), GetFunctionName() ); + return; + } + + for( int ix = 0; ix < numParms; ++ix ) { + parmType = GetParmType( ix ); + parm = GetParm( ix ); + if( !parmType->VerifyData(parm) ) { + gameLocal.Warning( "%s is not Type %s", parm, parmType->Name() ); + return; + } + } +} + +/* +================ +hhFuncParmAccessor::GetFunctionName +================ +*/ +const char* hhFuncParmAccessor::GetFunctionName() const { + return GetFunction()->Name(); +} + +/* +================ +hhFuncParmAccessor::GetFunction +================ +*/ +const function_t* hhFuncParmAccessor::GetFunction() const { + return function; +} + +/* +================ +hhFuncParmAccessor::GetReturnKey +================ +*/ +const char* hhFuncParmAccessor::GetReturnKey() const { + return returnKey.c_str(); +} + +/* +================ +hhFuncParmAccessor::GetReturnType +================ +*/ +idTypeDef* hhFuncParmAccessor::GetReturnType() const { + return GetFunction()->def->TypeDef()->ReturnType(); +} + +/* +================ +hhFuncParmAccessor::GetParm +================ +*/ +const char* hhFuncParmAccessor::GetParm( int index ) const { + return parms[ index ]; +} + +/* +================ +hhFuncParmAccessor::GetParmType +================ +*/ +idTypeDef* hhFuncParmAccessor::GetParmType( int index ) const { + return GetFunction()->def->TypeDef()->GetParmType( index ); +} + +/* +================ +hhFuncParmAccessor::GetParms +================ +*/ +const idList& hhFuncParmAccessor::GetParms() const { + return parms; +} + +/* +================ +hhFuncParmAccessor::GetParms +================ +*/ +idList& hhFuncParmAccessor::GetParms() { + return parms; +} + +/* +================ +hhFuncParmAccessor::SetFunction +================ +*/ +void hhFuncParmAccessor::SetFunction( const function_t* func ) { + function = func; +} + +/* +================ +hhFuncParmAccessor::SetParms +================ +*/ +void hhFuncParmAccessor::SetParms( const idList& parms ) { + this->parms = parms; +} + +/* +================ +hhFuncParmAccessor::SetReturnKey +================ +*/ +void hhFuncParmAccessor::SetReturnKey( const char* key ) { + returnKey = key; +} + +/* +================ +hhFuncParmAccessor::Save +================ +*/ +void hhFuncParmAccessor::Save( idSaveGame *savefile ) const { + int num = parms.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + savefile->WriteString( parms[i] ); + } + + savefile->WriteString( returnKey ); + savefile->WriteString( function ? function->Name() : "" ); +} + +/* +================ +hhFuncParmAccessor::Restore +================ +*/ +void hhFuncParmAccessor::Restore( idRestoreGame *savefile ) { + int num; + savefile->ReadInt( num ); + idStr tmp; + parms.Clear(); + for( int i = 0; i < num; i++ ) { + savefile->ReadString( tmp ); + parms.Append( tmp ); + } + + savefile->ReadString( returnKey ); + savefile->ReadString( tmp ); + if( tmp.Length() > 0 ) { + function = FindFunction( tmp ); + HH_ASSERT( function ); + } else { + function = NULL; + } +} + +/********************************************************************** + +hhTrigger + +**********************************************************************/ + +const idEventDef EV_Trigger("", "e"); +const idEventDef EV_Retrigger("", "e"); +const idEventDef EV_UnTrigger("", NULL); +const idEventDef EV_PollForUntouch("", NULL); + +CLASS_DECLARATION( idEntity, hhTrigger ) + EVENT( EV_Enable, hhTrigger::Event_Enable ) + EVENT( EV_Disable, hhTrigger::Event_Disable ) + EVENT( EV_Activate, hhTrigger::Event_Activate) + EVENT( EV_Deactivate, hhTrigger::Event_Deactivate) + EVENT( EV_Touch, hhTrigger::Event_Touch) + EVENT( EV_Trigger, hhTrigger::Event_TriggerAction ) + EVENT( EV_Retrigger, hhTrigger::Event_Retrigger ) + EVENT( EV_UnTrigger, hhTrigger::Event_UnTriggerAction ) + EVENT( EV_PollForUntouch, hhTrigger::Event_PollForUntouch ) + EVENT( EV_PostSpawn, hhTrigger::Event_PostSpawn ) +END_CLASS + +/* +================ +hhTrigger::Activate + +the trigger was just activated by activator +================ +*/ +void hhTrigger::Activate( idEntity *activator ) { + int triggerDelay = 0; + + unTriggerActivator = activator; + + // if not enabled, return + if( !bEnabled ) { + return; + } + + bActive = true; + + 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 ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + triggerDelay = hhMath::hhMax( 0, SEC2MS(delay + randomDelay * gameLocal.random.CRandomFloat()) ); + nextTriggerTime = gameLocal.time + triggerDelay + 1; + PostEventMS( &EV_Trigger, triggerDelay, activator ); + } else { + //Changed so we check to see if the activator is still with in the trigger volume + ProcessEvent( &EV_Trigger, activator ); + } +} + +/* +================ +hhTrigger::Spawn +================ +*/ +void hhTrigger::Spawn(void) { + + unTriggerActivator=NULL; + bActive=false; + + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "randomeDelay", "0", randomDelay ); + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + triggerBehavior = (triggerBehavior_t)spawnArgs.GetInt( "triggerBehavior" ); + spawnArgs.GetBool( "noTouch", "0", noTouch ); + spawnArgs.GetFloat( "uncalldelay", "0.2", untouchGranularity ); + spawnArgs.GetFloat( "refire", "-1", refire ); + spawnArgs.GetBool( "enabled", "1", initiallyEnabled ); + spawnArgs.GetBool( "untrigger", "0", bUntrigger ); + spawnArgs.GetBool( "always_trigger", "0", alwaysTrigger ); + spawnArgs.GetBool( "isSimpleBox", "1", isSimpleBox ); + spawnArgs.GetBool( "noVehicles", "0", bNoVehicles ); + + GetTriggerClasses( spawnArgs ); + + nextTriggerTime = 0; + + PostEventMS( (initiallyEnabled) ? &EV_Enable : &EV_Disable, 0); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +hhTrigger::Save +================ +*/ +void hhTrigger::Save( idSaveGame *savefile ) const { + savefile->WriteBool( bActive ); + savefile->WriteBool( bEnabled ); + savefile->WriteFloat( untouchGranularity ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( randomDelay ); + savefile->WriteFloat( refire ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteBool( alwaysTrigger ); + savefile->WriteBool( isSimpleBox ); + savefile->WriteBool( bNoVehicles ); + savefile->WriteStaticObject( funcInfo ); + savefile->WriteStaticObject( unfuncInfo ); + savefile->WriteStaticObject( funcRefInfo ); + savefile->WriteStaticObject( unfuncRefInfo ); + savefile->WriteStaticObject( funcRefActivatorInfo ); + savefile->WriteStaticObject( unfuncRefActivatorInfo ); + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteBool( noTouch ); + savefile->WriteBool( initiallyEnabled ); + savefile->WriteBool( bUntrigger ); + + unTriggerActivator.Save( savefile ); + + savefile->WriteStringList( TriggerClasses ); + savefile->WriteInt( triggerBehavior ); +} + +/* +================ +hhTrigger::Restore +================ +*/ +void hhTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( bActive ); + savefile->ReadBool( bEnabled ); + savefile->ReadFloat( untouchGranularity ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( randomDelay ); + savefile->ReadFloat( refire ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadBool( alwaysTrigger ); + savefile->ReadBool( isSimpleBox ); + savefile->ReadBool( bNoVehicles ); + savefile->ReadStaticObject( funcInfo ); + savefile->ReadStaticObject( unfuncInfo ); + savefile->ReadStaticObject( funcRefInfo ); + savefile->ReadStaticObject( unfuncRefInfo ); + savefile->ReadStaticObject( funcRefActivatorInfo ); + savefile->ReadStaticObject( unfuncRefActivatorInfo ); + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadBool( noTouch ); + savefile->ReadBool( initiallyEnabled ); + savefile->ReadBool( bUntrigger ); + + unTriggerActivator.Restore( savefile ); + + savefile->ReadStringList( TriggerClasses ); + savefile->ReadInt( reinterpret_cast ( triggerBehavior ) ); +} + +/* +================ +hhTrigger::CallFunctions +================ +*/ +void hhTrigger::CallFunctions( idEntity *activator ) { + funcInfo.CallFunction( spawnArgs ); + funcRefInfo.CallFunction( spawnArgs ); + + funcRefActivatorInfo.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self + funcRefActivatorInfo.CallFunction( spawnArgs ); +} + +/* +================ +hhTrigger::UncallFunctions +================ +*/ +void hhTrigger::UncallFunctions( idEntity *activator ) { + unfuncInfo.CallFunction( spawnArgs ); + unfuncRefInfo.CallFunction( spawnArgs ); + + unfuncRefActivatorInfo.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self + unfuncRefActivatorInfo.CallFunction( spawnArgs ); +} + +/* +================ +hhTrigger::TriggerAction +================ +*/ +void hhTrigger::TriggerAction( idEntity *activator ) { + + // Activate all targets + ActivateTargets( activator ); + + CallFunctions( activator ); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Disable, 0 ); + } + + // Handle refire + if (refire > 0) { + PostEventSec( &EV_Retrigger, refire + random * gameLocal.random.CRandomFloat(), activator); + } +} + +/* +================ +hhTrigger::UnTriggerAction +================ +*/ +void hhTrigger::UnTriggerAction() { + if( bUntrigger ) { + ActivateTargets( unTriggerActivator.GetEntity() ); + } + + UncallFunctions( unTriggerActivator.GetEntity() ); +} + +/* +=============== +hhTrigger::IsEncroaching +=============== +*/ +bool hhTrigger::IsEncroaching( const idEntity* entity ) { + + // nla - Simplified checks to bounds when possible. + // Error was due to contents of an idActivator not hitting anything here. (it has contents 0) + // But since the collide logic has been done elsewhere, we can simplify this to just a is this in this check. + if (isSimpleBox) { + // num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), entity->GetPhysics()->GetContents(), touch, MAX_GENTITIES ); + if ( GetPhysics()->GetAbsBounds().IntersectsBounds( entity->GetPhysics()->GetAbsBounds() ) ) { + return( true ); + } + } + else { + //num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, entity->GetPhysics()->GetContents() ); + if ( entity->GetPhysics()->ClipContents( GetPhysics()->GetClipModel() ) ) { + return( true ); + } + } + + return false; +} + +/* +=============== +hhTrigger::IsEncroached +=============== +*/ +bool hhTrigger::IsEncroached() { + idEntity *touch[ MAX_GENTITIES ]; + int num; + + if (isSimpleBox) { + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); + } + else { + num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES ); + } + + for (int i = 0; i < num; i++ ) { + if( touch[ i ] == this ) { + continue; + } + + if( CheckUnTriggerClass(touch[ i ]) ) { + return true; + } + } + return false; +} + +/* +=============== +hhTrigger::SetTriggerClasses +=============== +*/ +void hhTrigger::SetTriggerClasses( idList& list ) { + TriggerClasses.Clear(); + + TriggerClasses.SetNum(list.Num()); + + for(int iIndex = 0; iIndex < list.Num(); ++iIndex) { + TriggerClasses[iIndex] = list[iIndex]; + } +} + +/* +=============== +hhTrigger::CheckTriggerClass +=============== +*/ +bool hhTrigger::CheckTriggerClass( idEntity* activator ) { + if( !activator ) { + return false; + } + + if(!TriggerClasses.Num() || triggerBehavior == TB_ANY) { + return true; + } + + + for(int iIndex = 0; iIndex < TriggerClasses.Num(); ++iIndex) { + //Look for exact match then try for prefix match + if( !idStr(activator->spawnArgs.GetString("classname")).Icmp(TriggerClasses[iIndex].c_str()) || + !idStr(activator->spawnArgs.GetString("classname")).IcmpPrefix(TriggerClasses[iIndex].c_str()) ) { + + if( activator->IsType(hhPlayer::Type) ) {//Player needs client + hhPlayer* player = static_cast(activator); + if ( player->noclip || !player->ShouldTouchTrigger(this) ) { + return false; + } + if ( bNoVehicles && player->InVehicle() ) { + return false; + } + } + if( activator->IsType(idActor::Type) ) { // Dead monsters shouldn't keep refires going + if (activator->health <= 0) { + return false; + } + } + return true; + } + } + + return false; +} + +/* +=============== +hhTrigger::CheckUnTriggerClass + +Helper function used for overriding +=============== +*/ +bool hhTrigger::CheckUnTriggerClass(idEntity* activator) { + return CheckTriggerClass(activator); +} + +/* +=============== +hhTrigger::GetTriggerClasses +=============== +*/ +void hhTrigger::GetTriggerClasses(idDict& Args) { + const idKeyValue* pKeyValue = NULL; + int iNumKeyValues = Args.GetNumKeyVals(); + + TriggerClasses.Clear(); + + if(triggerBehavior == TB_PLAYER_ONLY) { + TriggerClasses.AddUnique( "player" ); + + } else if(triggerBehavior == TB_FRIENDLIES_ONLY) { + TriggerClasses.AddUnique( "player" ); + TriggerClasses.AddUnique( "character" ); + + } else if(triggerBehavior == TB_MONSTERS_ONLY) { + TriggerClasses.AddUnique( "monster" ); + + } else if(triggerBehavior == TB_PLAYER_MONSTERS_FRIENDLIES) { + TriggerClasses.AddUnique( "player" ); + TriggerClasses.AddUnique( "monster" ); + TriggerClasses.AddUnique( "character" ); + + } else if(triggerBehavior == TB_SPECIFIC_ENTITIES){ + for( int iIndex = 0; iIndex < iNumKeyValues; ++iIndex ) { + pKeyValue = Args.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "trigger_class", 13 ) ) { + TriggerClasses.AddUnique( pKeyValue->GetValue() ); + } + } + } +} + +/* +=============== +hhTrigger::Enable +=============== +*/ +void hhTrigger::Enable() { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + GetPhysics()->EnableClip(); + + bEnabled = true; +} + +/* +=============== +hhTrigger::Disable +=============== +*/ +void hhTrigger::Disable() { + // we may be relinked if we're bound to another object, so clear the contents as well + GetPhysics()->SetContents( 0 ); + GetPhysics()->DisableClip(); + + CancelEvents( &EV_Retrigger ); + bEnabled = false; +} + +/* +=============== +hhTrigger::Event_PostSpawn +=============== +*/ +void hhTrigger::Event_PostSpawn() { + funcInfo.ParseFunctionKeyValue( spawnArgs.GetString("call") ); + funcInfo.Verify(); + unfuncInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncall") ); + unfuncInfo.Verify(); + + funcRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("callRef") ); + funcRefInfo.InsertParm_Entity( this, 0 ); + funcRefInfo.Verify(); + + unfuncRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncallRef") ); + unfuncRefInfo.InsertParm_Entity( this, 0 ); + unfuncRefInfo.Verify(); + + funcRefActivatorInfo.ParseFunctionKeyValue( spawnArgs.GetString("callRefActivator") ); + funcRefActivatorInfo.InsertParm_Entity( this, 0 ); + // NOTE: Activator parm inserted at time of function call, therefore don't verify now + + unfuncRefActivatorInfo.ParseFunctionKeyValue( spawnArgs.GetString("uncallRefActivator") ); + unfuncRefActivatorInfo.InsertParm_Entity( this, 0 ); + // NOTE: Activator parm inserted at time of function call, therefore don't verify now +} + +/* +=============== +hhTrigger::Event_PollForUntouch +=============== +*/ +void hhTrigger::Event_PollForUntouch() { + if (!IsEncroached()) { + CancelEvents(&EV_PollForUntouch); + PostEventMS(&EV_Deactivate, 0); + } + else { + PostEventSec(&EV_PollForUntouch, untouchGranularity); + } +} + +/* +=============== +hhTrigger::Event_Touch +=============== +*/ +void hhTrigger::Event_Touch( idEntity *other, trace_t *trace ) { + + if( noTouch || !CheckTriggerClass(other) ) { + return; + } + + if( !IsActive() ) { + Activate( other ); + + // If this trigger uses any of the unTrigger mechanisms, start polling + if (bUntrigger || refire || unfuncInfo.GetFunction() || unfuncRefInfo.GetFunction() || unfuncRefActivatorInfo.GetFunction()) { + PostEventSec(&EV_PollForUntouch, untouchGranularity); + } + } + // If we have already triggered the first time, but should always trigger + else if ( alwaysTrigger ) { + Activate( other ); + } +} + +/* +================ +hhTrigger::Retrigger +================ +*/ +void hhTrigger::Event_Retrigger( idEntity *activator ) { + //AOB - needed to stop infinite retrigger when triggered from gui + //rww - activator can be null if removed before event is serviced. actually, isn't this a bad idea? maybe it's better to use an index. + if (!activator || (!noTouch && !IsEncroaching(activator)) ) { + CancelEvents( &EV_Retrigger ); + PostEventMS( &EV_Deactivate, 0 ); + return; + } + + // This is seperate event so we can cull them seperately from normal trigger events + + TriggerAction( activator ); +} + +/* +================ +hhTrigger::Event_UnTriggerAction +================ +*/ +void hhTrigger::Event_UnTriggerAction() { + UnTriggerAction(); +} + +/* +================ +hhTrigger::Event_TriggerAction +================ +*/ +void hhTrigger::Event_TriggerAction( idEntity *activator ) { + //HUMANHEAD rww - we are running into a null activator situation when the spirit proxy for the player runs into a trigger, + //and then the player switches back to his body and removes the proxy before the trigger fires (due to a relay/delay/whatever), + //as well as possibly any other situation where a delayed trigger is activated by an entity that gets removed before firing. + //it has been decided that in these situations, the world should be used as the activator. + if (!activator) { + activator = gameLocal.world; + } + //HUMANHEAD END + // Added noTouch && !IsEncroached to fix the issue with retriggered hurt constantly damaging the player, even when they weren't in it. + // nla - Added check to allow 'delayed' triggers to function when you weren't in them. + if (!noTouch && !IsEncroaching(activator) && !delay ) { + CancelEvents( &EV_Retrigger ); + PostEventMS( &EV_Deactivate, 0 ); + return; + } + + TriggerAction( activator ); +} + +/* +================ +hhTrigger::Event_Enable +================ +*/ +void hhTrigger::Event_Enable( void ) { + Enable(); +} + +/* +================ +hhTrigger::Event_Disable +================ +*/ +void hhTrigger::Event_Disable( void ) { + Disable(); +} + +/* +================ +hhTrigger::Event_Activate +================ +*/ +void hhTrigger::Event_Activate( idEntity *activator ) { + Activate( activator ); +} + +/* +================ +hhTrigger::Event_Deactivate +================ +*/ +void hhTrigger::Event_Deactivate() { + CancelEvents( &EV_Retrigger ); + PostEventMS( &EV_UnTrigger, 0 ); + bActive=false; +} + +/*********************************************************************** + +hhDamageTrigger + +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhDamageTrigger ) + EVENT( EV_Enable, hhDamageTrigger::Event_Enable ) + EVENT( EV_Disable, hhDamageTrigger::Event_Disable ) +END_CLASS + + +void hhDamageTrigger::Spawn(void) { + funcRefInfoDamage.ParseFunctionKeyValue( spawnArgs.GetString("damageCallRef") ); + funcRefInfoDamage.InsertParm_Entity( this, 0 ); +} + +void hhDamageTrigger::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( funcRefInfoDamage ); +} + +void hhDamageTrigger::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( funcRefInfoDamage ); +} + +void hhDamageTrigger::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) { + // Call script if requested with damageDef, used for spherebrain + if (funcRefInfoDamage.GetFunction() != NULL) { + funcRefInfoDamage.InsertParm_Entity( attacker, 1 ); + spawnArgs.Set( "last_damage", damageDefName ); + funcRefInfoDamage.CallFunction( spawnArgs ); + } + if ( spawnArgs.GetBool( "activate_targets" ) ) { + ActivateTargets( attacker ); + } + hhTrigger::Damage(inflictor, attacker, dir, damageDefName, damageScale, location); +} + +void hhDamageTrigger::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (!CheckTriggerClass(attacker)) { + health += damage; + return; + } + + if ( gameLocal.time < nextTriggerTime ) { + health += damage; + return; + } + + Activate( attacker ); +} + +void hhDamageTrigger::Event_Enable() { + bEnabled = true; + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); +} + +void hhDamageTrigger::Event_Disable() { + GetPhysics()->SetContents( 0 ); + fl.takedamage = false; + bEnabled = false; +} + +/*********************************************************************** + +hhTriggerPain + +***********************************************************************/ + +const idEventDef EV_DamageBox("damageBox", "e"); + +CLASS_DECLARATION( hhTrigger, hhTriggerPain ) + EVENT( EV_Activate, hhTriggerPain::Event_Activate ) + EVENT( EV_DamageBox, hhTriggerPain::Event_DamageBox ) +END_CLASS + +/* +================ +hhTriggerPain + + Damages activator + Can be turned on or off by enable/disable + Fires events, triggers targets +================ +*/ +void hhTriggerPain::Spawn(void) { + spawnArgs.GetBool( "apply_impulse", "0", applyImpulse ); +} + +void hhTriggerPain::Event_DamageBox( idEntity *activator ) { + idEntity *touch[ MAX_GENTITIES ]; + int num; + + if (isSimpleBox) { + num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); + } + else { + num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES ); + } + + for (int i = 0; i < num; i++ ) { + if( touch[ i ] == this ) { + continue; + } + + if( CheckTriggerClass( touch[i] ) ) { + touch[i]->Damage(activator, activator, vec3_origin, spawnArgs.GetString("def_damage"), 1.0f, INVALID_JOINT ); + } + } +} + +/* +================ +hhTriggerPain::TriggerAction +================ +*/ +void hhTriggerPain::TriggerAction(idEntity *activator) { + + if(activator) { + // If a player in a vehicle, apply pain to vehicle + if (activator->IsType(hhPlayer::Type)) { + hhPlayer *player = static_cast(activator); + if (player->InVehicle()) { + activator = player->GetVehicleInterface()->GetVehicle(); + } + } + + idVec3 Velocity = activator->GetPhysics()->GetLinearVelocity(); + + activator->Damage(NULL, NULL, -Velocity, spawnArgs.GetString("def_damage"), 1.0f, INVALID_JOINT ); + + if ( applyImpulse ) { +// activator->ApplyImpulse( this, 0, activator->GetPhysics()->GetOrigin(), -Velocity / 10.0 ); + activator->ApplyImpulse( this, 0, activator->GetPhysics()->GetOrigin(), -Velocity * (activator->GetPhysics()->GetMass()*1.5f) ); + } + + } + + // Handle default trigger behavior + hhTrigger::TriggerAction(activator); +} + +/* +================ +hhTriggerPain::Event_Activate +================ +*/ +void hhTriggerPain::Event_Activate( idEntity *activator ) { + //AOB - allow toggling +// if( IsEnabled() && (IsActive() || !IsEncroached()) ) { + if( IsEnabled() ) { + ProcessEvent( &EV_Disable ); + ProcessEvent( &EV_Deactivate ); + } else { + if( !IsEnabled() ) { + ProcessEvent( &EV_Enable ); + } + Activate( activator ); + } +} + +/* +================ +hhTriggerPain::Save +================ +*/ +void hhTriggerPain::Save( idSaveGame *savefile ) const { + savefile->WriteBool( applyImpulse ); +} + +/* +================ +hhTriggerPain::Restore +================ +*/ +void hhTriggerPain::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( applyImpulse ); +} + +/*********************************************************************** + +hhTriggerEnabler + +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhTriggerEnabler ) +END_CLASS + +/* +================ +hhTriggerEnabler + + When it is entered, target is enabled + When it is exited, target is disabled. +================ +*/ +void hhTriggerEnabler::Spawn(void) { +} + +/* +================ +hhTriggerEnabler::TriggerAction +================ +*/ +void hhTriggerEnabler::TriggerAction(idEntity *activator) { + + // Enable all targets + for (int t=0; tPostEventMS( &EV_Enable, 0 ); + } + } + + // Handle default trigger behavior + hhTrigger::TriggerAction(activator); +} + + +/* +================ +hhTriggerEnabler::UnTriggerAction +================ +*/ +void hhTriggerEnabler::UnTriggerAction() { + + // Disable all targets + for (int t=0; tPostEventMS( &EV_Disable, 0 ); + } + } + + // Handle default trigger behavior + hhTrigger::UnTriggerAction(); +} + + +/*********************************************************************** + +hhTriggerSight + + Activates targets when seen +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhTriggerSight ) + EVENT( EV_Enable, hhTriggerSight::Event_Enable ) + EVENT( EV_Disable, hhTriggerSight::Event_Disable ) +END_CLASS + + +void hhTriggerSight::Spawn(void) { + BecomeActive(TH_THINK); +} + +void hhTriggerSight::Think(void) { + + if (thinkFlags & TH_THINK) { + hhPlayer *player = NULL; + for (int i = 0; i < gameLocal.numClients; i++ ) { + player = static_cast(gameLocal.entities[ i ]); + if ( !player ) { + continue; + } + + // if there is no way we can see this player + if ( !gameLocal.InPlayerPVS(this) ) { + continue; + } + + if (player->CanSee(this, true)) { + + // activate + PostEventMS(&EV_Activate, 0, player); + + // Automatically disables itself, re-enable for repeat + PostEventMS(&EV_Disable, 0); + } + } + } + + Present(); // Need this so it get's removed from active list +} + +void hhTriggerSight::Event_Enable( void ) { + bEnabled = true; + BecomeActive(TH_THINK); +} + +void hhTriggerSight::Event_Disable( void ) { + bEnabled = false; + BecomeInactive(TH_THINK); +} + +/* +================ +hhTriggerSight::Save +================ +*/ +void hhTriggerSight::Save( idSaveGame *savefile ) const { + savefile->WriteInt( pvsArea ); +} + +/* +================ +hhTriggerSight::Restore +================ +*/ +void hhTriggerSight::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( pvsArea ); + Spawn(); +} + +/* +=============================================================================== + + hhTrigger_Count + +=============================================================================== +*/ + +CLASS_DECLARATION( hhTrigger, hhTrigger_Count ) +END_CLASS + +/* +================ +hhTrigger_Count::Save +================ +*/ +void hhTrigger_Count::Save( idSaveGame *savefile ) const { + savefile->WriteInt( goal ); + savefile->WriteInt( count ); + savefile->WriteFloat( delay ); +} + +/* +================ +hhTrigger_Count::Restore +================ +*/ +void hhTrigger_Count::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( goal ); + savefile->ReadInt( count ); + savefile->ReadFloat( delay ); +} + +/* +================ +hhTrigger_Count::Spawn +================ +*/ +void hhTrigger_Count::Spawn( void ) { + spawnArgs.GetInt( "count", "1", goal ); + spawnArgs.GetFloat( "delay", "0", delay ); + count = 0; +} + +/* +================ +hhTrigger_Count::Activate +================ +*/ +void hhTrigger_Count::Activate( idEntity *activator ) { + int triggerDelay = 0; + + unTriggerActivator = activator; + + // if not enabled, return + if (!bEnabled) { + return; + } + + bActive=true; + + 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 ( delay >= 0 ) { + // goal of -1 means trigger has been exhausted + if (goal >= 0) { + count++; + if ( count >= goal ) { + if (spawnArgs.GetBool("repeat")) { + count = 0; + } else { + goal = -1; + } + + // don't allow it to trigger again until our delay has passed + triggerDelay = hhMath::hhMax( 0, SEC2MS(delay + randomDelay * gameLocal.random.CRandomFloat()) ); + nextTriggerTime = gameLocal.time + triggerDelay; + PostEventMS( &EV_Trigger, triggerDelay, activator ); + } + } + } +} + +/* +================ +hhTrigger_Count::TriggerAction +================ +*/ +void hhTrigger_Count::TriggerAction( idEntity *activator ) { + hhTrigger::TriggerAction( activator ); + + if (goal == -1) { + PostEventMS( &EV_Remove, 0 ); + } +} + + +/* +=============================================================================== + + hhTrigger_Event + +=============================================================================== +*/ + +CLASS_DECLARATION( hhTrigger, hhTrigger_Event ) +END_CLASS + +/* +================ +hhTrigger_Event::Spawn +================ +*/ +void hhTrigger_Event::Spawn( void ) { + eventDef = FindEventDef( spawnArgs.GetString("eventDef") ); +} + +/* +================ +hhTrigger_Event::FindEventDef +================ +*/ +const idEventDef* hhTrigger_Event::FindEventDef( const char* eventDefName ) const { + function_t *func = hhTrigger_Event::FindFunction( eventDefName ); + if( !func || !func->eventdef ) { + gameLocal.Error( "Cannot find event %s on trigger %s\n", eventDefName, name.c_str() ); + } + + return func->eventdef; +} + +/* +================ +hhTrigger_Event::FindFunction +================ +*/ +function_t* hhTrigger_Event::FindFunction( const char* funcname ) { + function_t* func = NULL; + + func = gameLocal.program.FindFunction( funcname, gameLocal.program.FindType(funcname) ); + if( !func ) { + gameLocal.Error( "Cannot find function '%s' in hhTrigger", funcname ); + } + + return func; +} + +/* +================ +hhTrigger_Event::ActivateTargets +================ +*/ +void hhTrigger_Event::ActivateTargets( idEntity *activator ) const { + idEntity *ent; + int i; + + HH_ASSERT( eventDef ); + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( *eventDef ) ) { + ent->ProcessEvent( eventDef, activator ); + } + for (int ix=0; ixGetRenderEntity()->gui[ix]->Trigger(gameLocal.time); + } + } + } +} + +/*********************************************************************** + + hhMineTrigger + +***********************************************************************/ + +CLASS_DECLARATION( hhTrigger, hhMineTrigger ) +END_CLASS + +bool hhMineTrigger::CheckTriggerClass( idEntity* activator ) { + if( !activator ) { + return false; + } + + if(!TriggerClasses.Num() || triggerBehavior == TB_ANY) { + return true; + } + + for(int iIndex = 0; iIndex < TriggerClasses.Num(); ++iIndex) { + //Look for exact match then try for prefix match + if( !idStr(activator->spawnArgs.GetString("classname")).Icmp(TriggerClasses[iIndex].c_str()) || + !idStr(activator->spawnArgs.GetString("classname")).IcmpPrefix(TriggerClasses[iIndex].c_str()) ) { + + if( activator->IsType(hhPlayer::Type) ) {//Player needs client + hhPlayer* player = static_cast(activator); + if ( player->noclip || !player->ShouldTouchTrigger(this) ) { + return false; + } + if ( bNoVehicles && player->InVehicle() ) { + return false; + } + } + if( activator->IsType(idActor::Type) ) { // Dead monsters shouldn't keep refires going + if (activator->health <= 0) { + return false; + } + } + + return true; + } + } + + if ( activator->IsType(hhMountedGun::Type) ) { + return true; + } + + //HUMANHEAD jsh PCF 4/28/06 hardcode monsters to trigger mines + if ( !gameLocal.isMultiplayer && activator->IsType( idAI::Type ) && activator->health > 0 ) { + return true; + } + + return false; +} \ No newline at end of file diff --git a/neo/prey/Prey/game_trigger.h b/neo/prey/Prey/game_trigger.h new file mode 100644 index 00000000..c32c2d51 --- /dev/null +++ b/neo/prey/Prey/game_trigger.h @@ -0,0 +1,258 @@ +#ifndef __PREY_TRIGGER_H +#define __PREY_TRIGGER_H + +class hhFuncParmAccessor : public idClass { + CLASS_PROTOTYPE( hhFuncParmAccessor ); + +public: + hhFuncParmAccessor(); + explicit hhFuncParmAccessor( const hhFuncParmAccessor* accessor ); + explicit hhFuncParmAccessor( const hhFuncParmAccessor& accessor ); + + void SetInfo( const char* returnKey, const function_t* func, const idList& parms ); + + const char* GetFunctionName() const; + const function_t* GetFunction() const; + const char* GetReturnKey() const; + idTypeDef* GetReturnType() const; + const char* GetParm( int index ) const; + idTypeDef* GetParmType( int index ) const; + const idList& GetParms() const; + idList& GetParms(); + + void Verify(); + + static function_t* FindFunction( const char* funcname ); + void CallFunction( idDict& returnDict ); + void ParseFunctionKeyValue( const char* value ); + void ParseFunctionKeyValue( idList& valueList ); + +public: + void SetFunction( const function_t* func ); + void SetParms( const idList& parms ); + void SetReturnKey( const char* key ); + + void SetParm_Entity( const idEntity *ent, int index ); + void SetParm_String( const char *str, int index ); + + void InsertParm_String( const char *text, int index ); + void InsertParm_Float( float value, int index ); + void InsertParm_Int( int value, int index ); + void InsertParm_Vector( const idVec3 &vec, int index ); + void InsertParm_Entity( const idEntity *ent, int index ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void InsertParm( const char *text, int index ); + +protected: + idList parms; + idStr returnKey; + const function_t* function; +}; + +enum triggerBehavior_t { + TB_PLAYER_ONLY, + TB_FRIENDLIES_ONLY, + TB_MONSTERS_ONLY, + TB_PLAYER_MONSTERS_FRIENDLIES, + TB_SPECIFIC_ENTITIES, + TB_ANY, + NUM_BEHAVIORS +}; + +class hhTrigger : public idEntity +{ +public: + CLASS_PROTOTYPE( hhTrigger ); + + void Spawn( void ); + + virtual void Activate( idEntity *activator ); + virtual void TriggerAction( idEntity *activator ); + virtual void UnTriggerAction(); + bool IsEncroaching( const idEntity* entity ); + bool IsEncroached(); + + void SetTriggerClasses(idList& list); + virtual bool CheckTriggerClass(idEntity* activator); + virtual bool CheckUnTriggerClass(idEntity* activator); + void GetTriggerClasses(idDict& Args); + + virtual void Enable(); + virtual void Disable(); + + const bool IsActive() const { return bActive; } + const bool IsEnabled() const { return bEnabled; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void CallFunctions( idEntity *activator ); + void UncallFunctions( idEntity *activator ); + + virtual void Event_Enable(); + virtual void Event_Disable(); + void Event_Activate( idEntity *activator ); + void Event_Deactivate(); + virtual void Event_Touch( idEntity *other, trace_t *trace ); + void Event_TriggerAction( idEntity *activator ); + virtual void Event_Retrigger( idEntity *activator ); + void Event_PollForUntouch(); + virtual void Event_UnTriggerAction(); + void Event_PostSpawn(); + +public: + bool bActive; // Whether the trigger is currently touched + bool bEnabled; // whether the trigger is currently triggerable + float untouchGranularity; // Seconds between untouch polls + float wait; // Seconds before trigger is triggerable again + float random; // Random wait variance + float delay; // Seconds to delay the triggering + float randomDelay; + float refire; // Seconds before refiring trigger + int nextTriggerTime; // Time in ms when the trigger will be triggerable again + bool alwaysTrigger; // Do we want to always trigger, instead of just once + bool isSimpleBox; // Simple boxes can use cheaper bounds test for encroach checks + bool bNoVehicles; + + hhFuncParmAccessor funcInfo; + hhFuncParmAccessor unfuncInfo; + hhFuncParmAccessor funcRefInfo; + hhFuncParmAccessor unfuncRefInfo; + hhFuncParmAccessor funcRefActivatorInfo; + hhFuncParmAccessor unfuncRefActivatorInfo; + + idStr requires; // item that the trigger requires of the player + int removeItem; // whether to remove the required item from players inventory + bool noTouch; // whether to disregard touch as triggering mechanism + bool initiallyEnabled; // whether the trigger is initially triggerable + bool bUntrigger; // whether to resend the trigger message when untriggered + idEntityPtr unTriggerActivator; // Used solely for sending to Activate() when bUntrigger is set + + idList TriggerClasses; + + triggerBehavior_t triggerBehavior; +}; + +class hhDamageTrigger : public hhTrigger +{ +public: + CLASS_PROTOTYPE( hhDamageTrigger ); + + void Spawn( void ); + 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 ); + +protected: + virtual void Event_Enable(); + void Event_Disable(); + + hhFuncParmAccessor funcRefInfoDamage; +}; + + +class hhTriggerPain : public hhTrigger { +public : + CLASS_PROTOTYPE( hhTriggerPain ); + + void Spawn( void ); + virtual void TriggerAction(idEntity *activator); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_DamageBox( idEntity *activator ); + +protected: + bool applyImpulse; +}; + + +class hhTriggerEnabler : public hhTrigger { +public : + CLASS_PROTOTYPE( hhTriggerEnabler ); + + void Spawn( void ); + virtual void TriggerAction(idEntity *activator); + virtual void UnTriggerAction(void); +}; + + +class hhTriggerSight : public hhTrigger { +public: + CLASS_PROTOTYPE( hhTriggerSight ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + virtual void Think( void ); + virtual void Event_Enable( void ); + void Event_Disable( void ); + + int pvsArea; +}; + +/* +=============================================================================== + + Trigger which fires targets after being activated a specific number of times. + +=============================================================================== +*/ +class hhTrigger_Count : public hhTrigger { +public: + CLASS_PROTOTYPE( hhTrigger_Count ); + + // 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 Activate( idEntity *activator ); + virtual void TriggerAction( idEntity *activator ); + +protected: + int goal; + int count; + float delay; +}; + +class hhTrigger_Event : public hhTrigger { +public: + CLASS_PROTOTYPE( hhTrigger_Event ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const { } + void Restore( idRestoreGame *savefile ) { Spawn(); } + +protected: + virtual void ActivateTargets( idEntity *activator ) const; + + const idEventDef* FindEventDef( const char* eventDefName ) const; + static function_t* FindFunction( const char* funcname ); + +protected: + const idEventDef* eventDef; +}; + +class hhMineTrigger : public hhTrigger { + CLASS_PROTOTYPE( hhMineTrigger ); + +public: + virtual bool CheckTriggerClass(idEntity* activator); +}; + +#endif diff --git a/neo/prey/Prey/game_tripwire.cpp b/neo/prey/Prey/game_tripwire.cpp new file mode 100644 index 00000000..04728f01 --- /dev/null +++ b/neo/prey/Prey/game_tripwire.cpp @@ -0,0 +1,336 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +/********************************************************************** + +hhTriggerTripwire + +**********************************************************************/ +CLASS_DECLARATION( idEntity, hhTriggerTripwire ) + EVENT( EV_Enable, hhTriggerTripwire::Event_Enable ) + EVENT( EV_Disable, hhTriggerTripwire::Event_Disable ) + EVENT( EV_Activate, hhTriggerTripwire::Event_Activate ) + EVENT( EV_Deactivate, hhTriggerTripwire::Event_Deactivate ) +END_CLASS + +/* +================ +hhTriggerTripwire::Spawn +================ +*/ +void hhTriggerTripwire::Spawn( void ) { + m_fMaxBeamDistance = spawnArgs.GetFloat("lengthBeam", "4096"); + + m_CallFunc.ParseFunctionKeyValue( spawnArgs.GetString("call") ); + + m_CallFuncRef.ParseFunctionKeyValue( spawnArgs.GetString("callRef") ); + m_CallFuncRef.InsertParm_Entity( this, 0 ); + + m_CallFuncRefActivator.ParseFunctionKeyValue( spawnArgs.GetString("callRefActivator") ); + m_CallFuncRefActivator.InsertParm_Entity( this, 0 ); + + m_TriggerBehavior = (triggerBehavior_t)spawnArgs.GetInt("triggerBehavior"); + + GetPhysics()->GetContents( 0 ); + GetPhysics()->DisableClip(); + + // Spawn the eyebeam + m_pBeamEntity = hhBeamSystem::SpawnBeam( GetOrigin(), spawnArgs.GetString("beam") ); + if (m_pBeamEntity.IsValid()) { + m_pBeamEntity->SetOrigin( GetOrigin() ); + m_pBeamEntity->SetAxis( GetAxis() ); + m_pBeamEntity->Bind( this, true ); + } + + GetTriggerClasses( spawnArgs ); + + ToggleBeam( true ); + + m_pOwner = NULL; +} + +void hhTriggerTripwire::Save(idSaveGame *savefile) const { + savefile->WriteFloat(m_fMaxBeamDistance); + + savefile->WriteStaticObject(m_CallFunc); + savefile->WriteStaticObject(m_CallFuncRef); + savefile->WriteStaticObject(m_CallFuncRefActivator); + + m_pBeamEntity.Save(savefile); + + savefile->WriteInt(m_TriggerClasses.Num()); // idList + for (int i=0; iWriteString(m_TriggerClasses[i]); + } + + savefile->WriteInt(m_TriggerBehavior); + savefile->WriteObject( m_pOwner ); +} + +void hhTriggerTripwire::Restore( idRestoreGame *savefile ) { + int num; + + savefile->ReadFloat(m_fMaxBeamDistance); + + savefile->ReadStaticObject(m_CallFunc); + savefile->ReadStaticObject(m_CallFuncRef); + savefile->ReadStaticObject(m_CallFuncRefActivator); + + m_pBeamEntity.Restore(savefile); + + m_TriggerClasses.Clear(); + savefile->ReadInt(num); // idList + m_TriggerClasses.SetNum(num); + for (int i=0; iReadString(m_TriggerClasses[i]); + } + + savefile->ReadInt((int&)m_TriggerBehavior); + savefile->ReadObject( reinterpret_cast( m_pOwner ) ); +} + +/* +=============== +hhTriggerTripwire::~hhTriggerTripwire +=============== +*/ + +hhTriggerTripwire::~hhTriggerTripwire() { + SAFE_REMOVE( m_pBeamEntity ); +} + +/* +================ +hhSecurityEyeTripwire::SetOwner +================ +*/ +void hhTriggerTripwire::SetOwner( idEntity* pOwner ) { + m_pOwner = pOwner; +} + +/* +=============== +hhTriggerTripwire::NotifyTargets +=============== +*/ +void hhTriggerTripwire::NotifyTargets( idEntity* pActivator ) { + ActivateTargets( pActivator ); + + if( m_pOwner ) { + m_pOwner->ProcessEvent( &EV_Notify, pActivator ); + } +} + +/* +=============== +hhTriggerTripwire::ToggleBeam +=============== +*/ +void hhTriggerTripwire::ToggleBeam( bool bOn ) { + if( bOn ) { + BecomeActive( TH_TICKER ); + } + else { + BecomeInactive( TH_TICKER ); + } + + if( m_pBeamEntity.IsValid() ) { + m_pBeamEntity->Activate( bOn ); + } +} + +/* +=============== +hhTriggerTripwire::Ticker +=============== +*/ +void hhTriggerTripwire::Ticker() { + trace_t TraceInfo; + idEntity* pTraceTarget = NULL; + idVec3 TraceLength = GetAxis()[0] * m_fMaxBeamDistance; + idVec3 TraceEndPoint = GetOrigin() + TraceLength; + + gameLocal.clip.TracePoint( TraceInfo, GetOrigin(), TraceEndPoint, MASK_VISIBILITY, this ); + + // Update the beam system + if( m_pBeamEntity.IsValid() ) { + m_pBeamEntity->SetTargetLocation( TraceInfo.endpos ); + } + + if( TraceInfo.fraction < 1.0f && TraceInfo.c.entityNum < ENTITYNUM_MAX_NORMAL ) { + pTraceTarget = gameLocal.GetTraceEntity(TraceInfo); + if( CheckTriggerClass(pTraceTarget) ) { + CallFunctions( pTraceTarget ); + + NotifyTargets( pTraceTarget ); + + if( spawnArgs.GetBool("triggerOnce") ) { + PostEventMS( &EV_Disable, 0 ); + } + return; + } + } + + if(p_tripwireDebug.GetBool()) { + gameRenderWorld->DebugLine(colorRed, GetPhysics()->GetOrigin(), GetPhysics()->GetOrigin() + TraceLength * TraceInfo.fraction); + + gameRenderWorld->DebugBox( colorBlue, idBox(GetPhysics()->GetBounds(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()) ); + } +} + +/* +================ +hhTriggerTripwire::CallFunctions +================ +*/ +void hhTriggerTripwire::CallFunctions( idEntity *activator ) { + m_CallFunc.CallFunction( spawnArgs ); + m_CallFuncRef.CallFunction( spawnArgs ); + + m_CallFuncRefActivator.InsertParm_Entity( activator, 1 );//Needs to be second parm. After self + m_CallFuncRefActivator.CallFunction( spawnArgs ); +} + +/* +=============== +hhTriggerTripwire::SetTriggerClasses +=============== +*/ +void hhTriggerTripwire::SetTriggerClasses( idList& List ) { + m_TriggerClasses.Clear(); + + m_TriggerClasses.SetNum( List.Num() ); + + for(int iIndex = 0; iIndex < List.Num(); ++iIndex) { + m_TriggerClasses[iIndex] = List[iIndex]; + } +} + +/* +=============== +hhTriggerTripwire::CheckTriggerClass +=============== +*/ +bool hhTriggerTripwire::CheckTriggerClass( idEntity* pActivator ) { + if( !pActivator ) { + return false; + } + + if(!m_TriggerClasses.Num() || m_TriggerBehavior == TB_ANY) { + return true; + } + + for(int iIndex = 0; iIndex < m_TriggerClasses.Num(); ++iIndex) { + //Look for exact match then try for prefix match + if( !idStr(pActivator->spawnArgs.GetString("classname")).Icmp(m_TriggerClasses[iIndex].c_str()) || + !idStr(pActivator->spawnArgs.GetString("classname")).IcmpPrefix(m_TriggerClasses[iIndex].c_str()) ) { + if( pActivator && pActivator->IsType(hhPlayer::Type) ) {//Player needs client + hhPlayer* pPlayer = static_cast(pActivator); + if( !pPlayer || pPlayer->noclip || (pPlayer->GetPhysics()->GetClipMask() & MASK_SPIRITPLAYER) == MASK_SPIRITPLAYER ) { + return false; + } + } + // Vehicles are added for trigger behaviors including players, so here we check if the pilot is a player and should touch triggers + // Monsters piloting vehicles currently do not get scanned by tripwires + if( pActivator && pActivator->IsType(hhVehicle::Type) ) { + hhVehicle *pVehicle = static_cast(pActivator); + if( !pVehicle || pVehicle->IsNoClipping() || !pVehicle->GetPilot() || !pVehicle->GetPilot()->IsType(hhPlayer::Type) ) { + return false; + } + } + return true; + } + } + + return false; +} + +/* +=============== +hhTriggerTripwire::GetTriggerClasses +=============== +*/ +void hhTriggerTripwire::GetTriggerClasses( idDict& Args ) { + const idKeyValue* pKeyValue = NULL; + int iNumKeyValues = Args.GetNumKeyVals(); + + m_TriggerClasses.Clear(); + + switch( m_TriggerBehavior ) { + case TB_PLAYER_ONLY: + m_TriggerClasses.AddUnique( "player" ); + m_TriggerClasses.AddUnique( "vehicle" ); + break; + + case TB_FRIENDLIES_ONLY: + m_TriggerClasses.AddUnique( "player" ); + m_TriggerClasses.AddUnique( "vehicle" ); + m_TriggerClasses.AddUnique( "character" ); + break; + + case TB_MONSTERS_ONLY: + m_TriggerClasses.AddUnique( "monster" ); + break; + + case TB_PLAYER_MONSTERS_FRIENDLIES: + m_TriggerClasses.AddUnique( "player" ); + m_TriggerClasses.AddUnique( "vehicle" ); + m_TriggerClasses.AddUnique( "monster" ); + m_TriggerClasses.AddUnique( "character" ); + break; + + case TB_SPECIFIC_ENTITIES: + for( int iIndex = 0; iIndex < iNumKeyValues; ++iIndex ) { + pKeyValue = Args.GetKeyVal( iIndex ); + if ( !pKeyValue->GetKey().Cmpn( "trigger_class", 13 ) ) { + m_TriggerClasses.AddUnique( pKeyValue->GetValue() ); + } + } + break; + + default: + HH_ASSERT(!"Invalid trigger behavior!\n"); + break; + } +} + +/* +================ +hhTriggerTripwire::Event_Enable +================ +*/ +void hhTriggerTripwire::Event_Enable( void ) { + ToggleBeam( true ); +} + +/* +================ +hhTriggerTripwire::Event_Disable +================ +*/ +void hhTriggerTripwire::Event_Disable( void ) { + ToggleBeam( false ); +} + +/* +================ +hhTriggerTripwire::Event_Activate +================ +*/ +void hhTriggerTripwire::Event_Activate( idEntity *activator ) { + ToggleBeam( true ); +} + +/* +================ +hhTriggerTripwire::Event_Deactivate +================ +*/ +void hhTriggerTripwire::Event_Deactivate() { + ToggleBeam( false ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build diff --git a/neo/prey/Prey/game_tripwire.h b/neo/prey/Prey/game_tripwire.h new file mode 100644 index 00000000..f96d205a --- /dev/null +++ b/neo/prey/Prey/game_tripwire.h @@ -0,0 +1,50 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_TRIGGER_TRIPWIRE_H +#define __HH_TRIGGER_TRIPWIRE_H + +class hhTriggerTripwire : public idEntity { + CLASS_PROTOTYPE( hhTriggerTripwire ); + +public: + ~hhTriggerTripwire(); + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void ToggleBeam( bool bOn ); + void SetOwner( idEntity* pOwner ); + const idEntity* GetOwner() const { return m_pOwner; } + +protected: + virtual void NotifyTargets( idEntity* pActivator ); + + void SetTriggerClasses( idList& List ); + virtual bool CheckTriggerClass( idEntity* pActivator ); + void GetTriggerClasses( idDict& Args ); + + void CallFunctions( idEntity* activator ); + +protected: + virtual void Ticker(); + void Event_Enable(); + void Event_Disable(); + void Event_Activate( idEntity *activator ); + void Event_Deactivate(); + +public: + float m_fMaxBeamDistance; + +protected: + idEntity* m_pOwner; + hhFuncParmAccessor m_CallFunc; + hhFuncParmAccessor m_CallFuncRef; + hhFuncParmAccessor m_CallFuncRefActivator; + + idEntityPtr m_pBeamEntity; + + idList m_TriggerClasses; + triggerBehavior_t m_TriggerBehavior; +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/game_utils.cpp b/neo/prey/Prey/game_utils.cpp new file mode 100644 index 00000000..848a9a9f --- /dev/null +++ b/neo/prey/Prey/game_utils.cpp @@ -0,0 +1,749 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +////////////////////// +// hhMatterEventDefPartner +////////////////////// +hhMatterEventDefPartner::hhMatterEventDefPartner( const char* eventNamePrefix ) { + const char* event = NULL; + const idEventDef* eventDef = NULL; + surfTypes_t type = SURFTYPE_NONE; + + for( int ix = 0; ix < NUM_SURFACE_TYPES; ++ix ) { + type = (surfTypes_t)ix; + event = va( "<%s_%s>", eventNamePrefix, gameLocal.MatterTypeToMatterName(type) ); + eventDef = idEventDef::FindEvent( event ); + AddPartner( eventDef, type ); + } +} + + +////////////////////// +// hhUtils +////////////////////// + + + +int hhUtils::ContentsOfBounds(const idBounds &localBounds, const idVec3 &location, const idMat3 &axis, idEntity *pass) { + idTraceModel trm; + trm.SetupBox(localBounds); + idClipModel *clipModel = new idClipModel(trm); + int contents = gameLocal.clip.Contents(location, clipModel, axis, MASK_ALL, pass); + delete clipModel; + return contents; +} + +/* +==================== +hhUtils::EntityDefWillFit + Check whether a given entity def will fit in a location if spawned, pass the contents that would block it +==================== +*/ +bool hhUtils::EntityDefWillFit( const char *defName, const idVec3 &location, const idMat3 &axis, int contentMask, idEntity *passEntity ) { + idBounds localBounds; + idVec3 size; + + // Calculate a local bounds for object + const idDict *dict = gameLocal.FindEntityDefDict(defName, false); + localBounds.Zero(); + if (dict) { + if ( dict->GetVector( "mins", NULL, localBounds[0] ) && + dict->GetVector( "maxs", NULL, localBounds[1] ) ) { + } else if ( dict->GetVector( "size", NULL, size ) ) { + localBounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + localBounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + } else { // Default bounds + localBounds.Expand( 1.0f ); + } + } + + // Determine contents of the bounds + idTraceModel trm; + trm.SetupBox(localBounds); + idClipModel *clipModel = new idClipModel(trm); + int contents = gameLocal.clip.Contents(location, clipModel, axis, contentMask, passEntity); + delete clipModel; + +/* if (contents & CONTENTS_SOLID) { + gameRenderWorld->DebugBounds( colorRed, localBounds, location, 5000); + } + else if (contents == 0) { + gameRenderWorld->DebugBounds( colorGreen, localBounds, location, 5000); + } + else { + gameRenderWorld->DebugBounds( colorYellow, localBounds, location, 5000); + }*/ + + return (contents == 0); +} + + +/* +==================== +hhUtils::SpawnDebrisMass +==================== +*/ +void hhUtils::SpawnDebrisMass( const char *debrisMassEntity, + const idVec3 &origin, + const idVec3 *orientation, + const idVec3 *velocity, + const int power, + bool nonsolid, + float *duration, + idEntity *entForBounds ) { //HUMANHEAD rww - added entForBounds + + idDict args; + idVec3 powerVector; + + + if ( !debrisMassEntity || !debrisMassEntity[0] ) { + gameLocal.Warning( "Invalid Debris Entity called (%p)", debrisMassEntity ); + return; + } + + args.SetVector( "origin", origin ); + + // Set the values to the dictionary + if ( orientation ) { + args.SetVector( "orientation", *orientation ); + } + + if ( power >= 0 ) { + powerVector.x = powerVector.y = powerVector.z = power; + args.SetVector( "power", powerVector ); + } + + if ( nonsolid ) { + args.Set( "nonsolid", "1" ); + } + + if ( velocity ) { + args.SetVector( "velocity", *velocity ); + } + + if (entForBounds) { //HUMANHEAD rww + args.SetBool("spawnUsingEntity", true); + args.SetBool("useAFBounds", true); + } + + // Spawn the object + idEntity *ent = gameLocal.SpawnObject( debrisMassEntity, &args ); + + if ( duration && ent->IsType( hhDebrisSpawner::Type ) ) { + hhDebrisSpawner *dspawner = static_cast ( ent ); + + if (entForBounds) { //HUMANHEAD rww + dspawner->Activate(entForBounds); + } + + *duration = dspawner->GetDuration(); + } +} + + +/* +==================== +hhUtils::SpawnDebrisMass +==================== +*/ +void hhUtils::SpawnDebrisMass( const char *debrisMassEntity, + idEntity *sourceEntity, + const int power ) { + + idEntity *mass; + idDict args; + + + if ( !debrisMassEntity || !debrisMassEntity[0] ) { + gameLocal.Warning( "Invalid Debris Entity called (%p).", debrisMassEntity ); + return; + } + + args.SetVector( "origin", sourceEntity->GetOrigin() ); + args.SetVector( "orientation", sourceEntity->GetPhysics()->GetAxis()[0] ); + args.SetVector( "velocity", sourceEntity->GetPhysics()->GetLinearVelocity() ); + + if ( power >= 0 ) { + args.SetInt( "power", power ); + } + + args.SetInt( "spawnUsingEntity" , 1 ); + + + // Spawn the object + mass = gameLocal.SpawnObject( debrisMassEntity, &args ); + if ( mass ) { + if ( mass->IsType( hhDebrisSpawner::Type ) ) { + ( ( hhDebrisSpawner * ) mass )->Activate( sourceEntity ); + } + } + else { + gameLocal.Printf("Error spawning debris mass: %s\n", debrisMassEntity ); + } + +} + + +hhProjectile *hhUtils::LaunchProjectile(idEntity *attacker, + const char *projectile, + const idMat3 &axis, + const idVec3 &origin ) { + + hhProjectile *proj=NULL; + idDict args; + args.Clear(); + args.Set( "classname", projectile ); + args.Set( "origin", origin.ToString() ); + + idEntity *ent=NULL; + if (gameLocal.SpawnEntityDef( args, &ent ) && ent && ent->IsType(hhProjectile::Type)) { + proj = ( hhProjectile * )ent; + proj->Create( attacker, origin, axis ); + proj->Launch( origin, axis, vec3_origin ); + } + + return proj; +} + +void hhUtils::DebugCross( const idVec4 &color, const idVec3 &start, int size, const int lifetime ) { + static idVec3 xaxis(1.0f,0.0f,0.0f); + static idVec3 yaxis(0.0f,1.0f,0.0f); + static idVec3 zaxis(0.0f,0.0f,1.0f); + gameRenderWorld->DebugLine(color, start-xaxis*size, start+xaxis*size, lifetime); + gameRenderWorld->DebugLine(color, start-yaxis*size, start+yaxis*size, lifetime); + gameRenderWorld->DebugLine(color, start-zaxis*size, start+zaxis*size, lifetime); +} + +void hhUtils::DebugAxis( const idVec3 &origin, const idMat3 &axis, int size, const int lifetime ) { + gameRenderWorld->DebugArrow(colorRed, origin, origin+axis[0]*size, 5, lifetime); + gameRenderWorld->DebugArrow(colorGreen, origin, origin+axis[1]*size, 5, lifetime); + gameRenderWorld->DebugArrow(colorBlue, origin, origin+axis[2]*size, 5, lifetime); +} + + +idVec3 hhUtils::RandomVector() { + idVec3 vec; + vec.x = gameLocal.random.CRandomFloat(); + vec.y = gameLocal.random.CRandomFloat(); + vec.z = gameLocal.random.CRandomFloat(); + return vec; +} + +float hhUtils::RandomSign() { + return gameLocal.random.RandomFloat() < 0.5f ? -1.0f : 1.0f; +} + +idVec3 hhUtils::RandomPointInBounds(idBounds &bounds) { + idVec3 point; + point.x = bounds[0].x + (bounds[1].x - bounds[0].x) * gameLocal.random.RandomFloat(); + point.y = bounds[0].y + (bounds[1].y - bounds[0].y) * gameLocal.random.RandomFloat(); + point.z = bounds[0].z + (bounds[1].z - bounds[0].z) * gameLocal.random.RandomFloat(); + return point; +} + +idVec3 hhUtils::RandomPointInShell( const float innerRadius, const float outerRadius ) { + idAngles dir( gameLocal.random.RandomFloat() * 360.0f, gameLocal.random.RandomFloat() * 360.0f, gameLocal.random.RandomFloat() * 360.0f ); + return dir.ToForward() * (innerRadius + (outerRadius - innerRadius) * gameLocal.random.RandomFloat()); +} + +/* +================= +hhUtils::SplitString + Strips out the first element which is usually the original command +================= +*/ +void hhUtils::SplitString( const idCmdArgs& input, idList& pieces ) { + int numParms = input.Argc() - 1; + for( int ix = 0; ix < numParms; ++ix ) { + pieces.Append( input.Argv(ix + 1) ); + } +} + +/* +================= +hhUtils::SplitString + Takes a comma seperated string, and returns the bits between the commas + Spaces on either side of the comma are stripped off + The 'pieces' list is not cleared. +================= +*/ +void hhUtils::SplitString( const idStr& input, idList& pieces, const char delimiter ) { + idStr element; + int endIndex = -1; + const char groupDelimiter = '\''; + char currentChar = '\0'; + + for( int startIndex = 0; startIndex <= input.Length(); ++startIndex ) { + currentChar = input[ startIndex ]; + if (currentChar) { + if( currentChar == groupDelimiter ) { + endIndex = input.Find( currentChar, startIndex + 1 ); + element = input.Mid( startIndex + 1, endIndex - startIndex - 1 ); + + startIndex = endIndex; + + pieces.Append( element ); + element.Clear(); + continue; + } else if( currentChar == delimiter ) { + element += '\0'; + pieces.Append( element ); + element.Clear(); + continue; + } + + element += currentChar; + } + } + + if( element.Length() ) { + pieces.Append( element ); + } +} + + +/* +================= +hhUtils::GetValues +Takes a source dict, and a key base string, and returns all values for keys with that base. ie: + + "base" "b" + "base1" "b1" + "base4" "b4" + "base_bob" "bob" + + is the dict. + +The option 'numericOnly' restricts the keys to be either an exact match, or a numeric addition + + hhUtils::GetValues( dict, "base", strList, true ); + +strList would contain: "b", "b1", "b4" + +while: + + hhUtils::GetValues( dict, "base", strList ); + +strList would contain: "b", "b1", "b4", "bob" + +================= +*/ +void hhUtils::GetValues( idDict &source, const char *keyBase, idList &values, bool numericOnly ) { + idList keys; + + GetKeysAndValues( source, keyBase, keys, values, numericOnly ); +} // hhUtils::GetValues( idDict &, const char *, idList, [bool] ) + + +/* +================= +hhUtils::GetKeys +Takes a source dict, and a key base string, and returns all keys with that base. ie: + + "base" "b" + "base1" "b1" + "base4" "b4" + "base_bob" "bob" + + is the dict. + +The option 'numericOnly' restricts the keys to be either an exact match, or a numeric addition + + hhUtils::GetValues( dict, "base", strList, true ); + +strList would contain: "base", "base1", "base4" + +while: + + hhUtils::GetValues( dict, "base", strList ); + +strList would contain: "base", "base1", "base4", "base_bob" + +================= +*/ +void hhUtils::GetKeys( idDict &source, const char *keyBase, idList &keys, bool numericOnly ) { + idList values; + + GetKeysAndValues( source, keyBase, keys, values, numericOnly ); +} + + +/* +============== +hhUtils::getKeysAndValues +============== +*/ +void hhUtils::GetKeysAndValues( idDict &source, const char *keyBase, idList &keys, idList &values, bool numericOnly ) { + const idKeyValue * kv = NULL; + idStr keyEnd; + + + for ( kv = source.MatchPrefix( keyBase, kv ); + kv && kv->GetValue().Length(); + kv = source.MatchPrefix( keyBase, kv ) ) { + keyEnd = kv->GetKey(); + keyEnd.Strip( keyBase ); + + // Is a valid debris base And it isn't a variation. (ie, contains a .X postfix) + if ( !numericOnly || ( idStr::IsNumeric( keyEnd ) && keyEnd.Find( '.' ) < 0 ) ) { + //gameLocal.Printf( "Adding %s => %s\n", (const char *) kv->GetKey(), (const char *) kv->GetValue() ); + keys.Append( kv->GetKey() ); + values.Append( kv->GetValue() ); + } + + } + +} + +/* +================= +hhUtils::RandomSpreadDir +================= +*/ +idVec3 hhUtils::RandomSpreadDir( const idMat3& baseAxis, const float spread ) { + float ang = hhMath::Sin( spread * gameLocal.TimeBasedRandomFloat() ); + float spin = hhMath::TWO_PI * gameLocal.TimeBasedRandomFloat(); + idVec3 dir = baseAxis[ 0 ] + baseAxis[ 2 ] * ( ang * hhMath::Sin(spin) ) - baseAxis[ 1 ] * ( ang * hhMath::Cos(spin) ); + dir.Normalize(); + + return dir; +} + +//----------------------------------------------------- +// ProjectOntoScreen +// +// Project a world position onto screen +//----------------------------------------------------- +idVec3 hhUtils::ProjectOntoScreen(idVec3 &world, const renderView_t &renderView) { + idVec3 pdc(-1000.0f, -1000.0f, -1.0f); + + // Convert world -> camera + idVec3 view = ( world - renderView.vieworg ) * renderView.viewaxis.Inverse(); + + // Orient from doom coords to camera coords (look down +Z) + idVec3 cam; + cam.x = -view[1]; + cam.y = -view[2]; + cam.z = view[0]; + + if (cam.z > 0.0f) { + // Adjust for differing FOVs + float halfwidth = renderView.width * 0.5f; + float halfheight = renderView.height * 0.5f; + float f = halfwidth / tan( renderView.fov_x * 0.5f * idMath::M_DEG2RAD ); + float g = halfheight / tan( renderView.fov_y * 0.5f * idMath::M_DEG2RAD ); + + // Project onto screen + pdc[0] = (cam.x * f / cam.z) + halfwidth; + pdc[1] = (cam.y * g / cam.z) + halfheight; + pdc[2] = cam.z; + } + + return pdc; // negative Z indicates behind the view +} + + +/* +================= +hhUtils::GetLocalGravity +================= +*/ +idVec3 hhUtils::GetLocalGravity( const idVec3& origin, const idBounds& bounds, const idVec3& defaultGravity ) { + idEntity* entityList[ MAX_GENTITIES ]; + idEntity* entity = NULL; + hhGravityZoneBase* zone = NULL; + + int numEntities = gameLocal.clip.EntitiesTouchingBounds( bounds.Translate(origin), CONTENTS_TRIGGER, entityList, MAX_GENTITIES ); + for( int ix = 0; ix < numEntities; ++ix ) { + entity = entityList[ ix ]; + + if( !entity ) { + continue; + } + + if( !entity->IsType(hhGravityZoneBase::Type) ) { + continue; + } + + zone = static_cast( entity ); + if( !zone->IsActive() || !zone->IsEnabled() ) { + continue; + } + + // More expensive check if non-simple zone + if ( !zone->isSimpleBox ) { + idTraceModel *playerTrm = new idTraceModel(bounds); + idClipModel *playerModel = new idClipModel( *playerTrm ); + playerModel->SetContents(CONTENTS_TRIGGER); + + idClipModel *zoneModel = zone->GetPhysics()->GetClipModel(); + + int contents = gameLocal.clip.ContentsModel( playerModel->GetOrigin(), playerModel, playerModel->GetAxis(), -1, + zoneModel->Handle(), zoneModel->GetOrigin(), zoneModel->GetAxis() ); + + delete playerModel; + delete playerTrm; + if ( !contents ) { + continue; + } + } + + return zone->GetCurrentGravity( origin ); + } + + return defaultGravity; +} + +//============================================================================= +// +// PointToAngle +// +// Point should be about the origin +//============================================================================= +float hhUtils::PointToAngle(float x, float y) +{ + if ( x == 0 && y == 0 ) { + return 0; + } + + if ( x >= 0 ) { // x >= 0 + if (y >= 0 ) { // y >= 0 + if ( x > y ) { + return atan( y / x ); // octant 0 + } else { + return DEG2RAD(90) - atan( x / y ); // octant 1 + } + } + else { // y < 0 + y = -y; + if ( x > y ) { + return -atan( y / x ); // octant 8 + } else { + return DEG2RAD(270) + atan( x / y ); // octant 7 + } + } + } + else { // x < 0 + x = -x; + if ( y >= 0 ) { // y>= 0 + if ( x > y ) { + return DEG2RAD(180) - atan( y / x ); // octant 3 + } + else { + return DEG2RAD(90) + atan( x / y ); // octant 2 + } + } + else { // y < 0 + y = -y; + if ( x > y ) { + return DEG2RAD(180) + atan( y / x ); // octant 4 + } else { + return DEG2RAD(270) - atan( x / y ); // octant 5 + } + } + } + + return 0; +} + +float hhUtils::DetermineFinalFallVelocityMagnitude( const float totalFallDist, const float gravity ) { + if( hhMath::Fabs(gravity) <= VECTOR_EPSILON ) { + return 0.0f; + } + + return gravity * hhMath::Sqrt( 2.0f * totalFallDist / gravity ); +} + +/* +====================== +hhUtils::ChannelName2Num +returns -1 if not found +HUMANHEAD nla +====================== +*/ +int hhUtils::ChannelName2Num( const char *name, const idDict *entityDef ) { + int num; + + entityDef->GetInt( va( "channel2num_%s", name ), "-1", num ); + + return( num ); +} + + +/* +================ +hhUtils::CreateFxDefinition + +//HUMANHEAD: aob - used for our wound system +================ +*/ +void hhUtils::CreateFxDefinition( idStr &definition, const char* smokeName, const float duration ) { + + definition.Clear(); + + if( !smokeName || !smokeName[0] ) { + return; + } + + definition = va( + "fx %s // IMPLICITLY GENERATED\n" + "{ {\n" + "name \"%s\"\n" + "duration %.2f\n" + "delay 0\n" + "restart 0\n" + "particle \"%s.prt\"\n" + "} }\n", smokeName, smokeName, duration, smokeName); +} + +/* +================ +hhUtils::CalculateSoundVolume + Calculate a linear volume from a velocity magnitude and min/max range +================ +*/ +float hhUtils::CalculateSoundVolume( const float value, const float min, const float max ) { + float linear; + float decibels; + + if( min == max ) { + return 0.0f; + } + + linear = hhMath::ClampFloat( 0.0f, 1.0f, (value - min) / (max - min) ); + linear = (linear * 0.95f) + 0.05f; // Make lower end -30dB not -60dB + + decibels = hhMath::Scale2dB(linear); + decibels -= 3; + linear = hhMath::dB2Scale(decibels); + + return linear; +} + +/* +================ +hhUtils::CalculateScale +================ +*/ +float hhUtils::CalculateScale( const float value, const float min, const float max ) { + if( min == max ) { + return 0.0f; + } + + return hhMath::ClampFloat( 0.0f, 1.0f, (value - min) / (max - min) ); +} + +// Find all entities overlapping this clipmodel +// Pass a contents to limit the entities found to those matching that contents +int hhUtils::EntitiesTouchingClipmodel( idClipModel *clipModel, idEntity **entityList, int maxCount, int contents ) { + int i, j, numClipModels, numEntities; + idClipModel * clipModels[ MAX_GENTITIES ]; + idClipModel * cm; + idEntity * ent; + + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipModel->GetAbsBounds(), contents, clipModels, MAX_GENTITIES ); + numEntities = 0; + + // For each entity in bounds of our clipModel + // test if entity overlaps the clipModel + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModels[ i ]; + + if (cm == clipModel) { + continue; + } + + ent = cm->GetEntity(); + + if (!ent || !ent->GetPhysics()->GetClipModel()->IsTraceModel()) { + // Can only test versus trace models + continue; + } + + // if the entity is not yet in the list + for ( j = 0; j < numEntities; j++ ) { + if ( entityList[j] == ent ) { + break; + } + } + if ( j < numEntities ) { + continue; + } + + // Test if entity clipmodel overlaps our clipmodel + if ( !ent->GetPhysics()->ClipContents( clipModel ) ) { + continue; + } + + // add entity to the list + if ( numEntities >= maxCount ) { + gameLocal.Warning( "hhUtils::EntitiesTouchingClipmodel: max count" ); + break; + } + entityList[numEntities++] = ent; + + } + + return numEntities; +} + +idBounds hhUtils::ScaleBounds( const idBounds& bounds, float scale ) { + idBounds localBounds; + + if( scale <= 0.0 || scale == 1.0f ) { + return bounds; + } + + //Put bounds at origin so our scale is done correctly + localBounds = bounds.Translate( -bounds.GetCenter() ); + + for( int ix = 0; ix < 2; ++ix ) { + localBounds[ix] *= scale; + } + + return localBounds; +} + +idVec3 hhUtils::DetermineOppositePointOnBounds( const idVec3& start, const idVec3& dir, const idBounds& bounds ) { + float scale = 0.0f; + float radius = bounds.GetRadius(); + + if( !bounds.RayIntersection(start, dir, scale) ) { + return start; + } + + if( !bounds.RayIntersection(start + dir * radius, -dir, scale) ) { + return start; + } + + return start + dir * (radius - scale); +} + +/* +============= +hhUtils::PassArgs + Take any args that begin with prefix, and pass them into dict +============= +*/ + +void hhUtils::PassArgs( const idDict &source, idDict &dest, const char *passPrefix ) { + const idKeyValue * kv = NULL; + idStr indexStr; + + // Loop through looking for the next valid key to pass + for ( kv = source.MatchPrefix( passPrefix, kv ); + kv && kv->GetValue().Length(); + kv = source.MatchPrefix( passPrefix, kv ) ) { + indexStr = kv->GetKey(); + indexStr.Strip( passPrefix ); + +// gameLocal.Printf( "Passing %s => %s\n", +// (const char *) indexStr, +// (const char *) kv->GetValue() ); + + dest.Set( indexStr, kv->GetValue() ); + } +} + diff --git a/neo/prey/Prey/game_utils.h b/neo/prey/Prey/game_utils.h new file mode 100644 index 00000000..7a69afb5 --- /dev/null +++ b/neo/prey/Prey/game_utils.h @@ -0,0 +1,318 @@ + +#ifndef __GAME_UTILS_H__ +#define __GAME_UTILS_H__ + +//Helper defines to allow line commenting with certain defines. +#define COMMENT SLASH(/) +#define SLASH(s) /##s + +////////////////////// +// hhMatterPartner +////////////////////// +template< class PartnerType > +class hhMatterPartner { +public: + const PartnerType& GetPartner( const idEntity* ent, const idMaterial* material ) const; + const PartnerType& GetPartner( surfTypes_t type ) const; + +protected: + void AddPartner( PartnerType partner, surfTypes_t type ); + +protected: + // HUMANHEAD mdl: This doesn't need to be saved because it's generated automatically by the constructor. I think. + idList< PartnerType > matterPartnerList; +}; + +template< class PartnerType > +const PartnerType& hhMatterPartner::GetPartner( const idEntity* ent, const idMaterial* material ) const { + return GetPartner( gameLocal.GetMatterType(ent, material) ); +} + +template< class PartnerType > +const PartnerType& hhMatterPartner::GetPartner( surfTypes_t type ) const { + return matterPartnerList[ (int)type ]; +} + +template< class PartnerType > +void hhMatterPartner::AddPartner( PartnerType partner, surfTypes_t type ) { + matterPartnerList.Insert( partner, (int)type ); +} + +////////////////////// +// hhMatterEventDefPartner +////////////////////// +class hhMatterEventDefPartner : public hhMatterPartner { +public: + hhMatterEventDefPartner( const char* eventNamePrefix ); +}; + + +////////////////////// +// hhUtils +////////////////////// +class hhUtils { +public: + static int ContentsOfBounds(const idBounds &localBounds, const idVec3 &location, const idMat3 &axis, idEntity *pass); + static bool EntityDefWillFit( const char *defName, const idVec3 &location, const idMat3 &axis, int contentMask, idEntity *pass ); + static void SpawnDebrisMass( const char *debrisEntity, + const idVec3 &origin, + const idVec3 *orientation = NULL, + const idVec3 *velocity = NULL, + const int power = -1, + bool nonsolid = false, + float *duration = NULL, + idEntity *entForBounds = NULL ); //HUMANHEAD rww - added entForBounds + + static void SpawnDebrisMass( const char *debrisEntity, + idEntity *sourceEntity, + const int power = -1 ); + + static hhProjectile * LaunchProjectile( idEntity *attacker, + const char *projectile, + const idMat3 &axis, + const idVec3 &origin ); + + static void DebugAxis( const idVec3 &origin, const idMat3 &axis, int size, const int lifetime ); + static void DebugCross( const idVec4 &color, + const idVec3 &start, + int size, + const int lifetime = 0 ); + + static idVec3 RandomVector(); + static float RandomSign(); + static idVec3 RandomSpreadDir( const idMat3& baseAxis, const float spread ); + + static idVec3 RandomPointInBounds(idBounds &bounds); + static idVec3 RandomPointInShell( const float innerRadius, const float outerRadius ); + + static void SplitString( const idCmdArgs& input, idList& pieces ); + static void SplitString( const idStr& input, idList& pieces, const char delimiter = ',' ); + + static idVec3 GetLocalGravity( const idVec3& origin, const idBounds& bounds, const idVec3& defaultGravity = gameLocal.GetGravity() ); + + static idVec3 ProjectOntoScreen(idVec3 &world, const renderView_t &renderView); + + static void GetValues( idDict& source, const char *keyBase, idList &values, bool numericOnly = false ); + + static void GetKeys( idDict& source, const char *keyBase, idList &keys, bool numericOnly = false ); + + static void GetKeysAndValues( idDict& source, const char *keyBase, idList &keys, idList &values, bool numericOnly = false ); + + static float DetermineFinalFallVelocityMagnitude( const float totalFallDist, const float gravity ); + + static int ChannelName2Num( const char *name, const idDict *entityDef ); + + static int EntitiesTouchingClipmodel(idClipModel *clipModel, idEntity **entityList, int maxCount, int contents=MASK_SHOT_BOUNDINGBOX ); + + static void PassArgs( const idDict &source, idDict &dest, const char *passPrefix = "pass_" ); + + static float PointToAngle(float x, float y); + + static idMat3 SwapXZ( const idMat3& axis ); + + static void CreateFxDefinition( idStr &definition, const char* smokeName, const float duration ); + + static float CalculateSoundVolume( const float value, const float min, const float max ); + + static float CalculateScale( const float value, const float min, const float max ); + + static idBounds ScaleBounds( const idBounds& bounds, float scale ); + + static idVec3 DetermineOppositePointOnBounds( const idVec3& start, const idVec3& dir, const idBounds& bounds ); + + template< class Type > + static void Swap( Type& Val1, Type& Val2 ); + + template< class listType > static ID_INLINE void RemoveContents( idList& list, bool clear ) { + for( int index = list.Num() - 1; index >= 0; --index ) { + SAFE_REMOVE( list[index] ); + } + if( clear ) { + list.Clear(); + } else { + memset( list.Ptr(), 0, list.MemoryUsed() ); + } + } +}; + +/* +================ +hhUtils::SwapXZ + +HUMANHEAD: aob +================ +*/ +ID_INLINE idMat3 hhUtils::SwapXZ( const idMat3& axis ) { + return idMat3( -axis[2], axis[1], axis[0] ); +} + + +/* +=============== +hhMath::Swap +=============== +*/ +template< class Type > +ID_INLINE void hhUtils::Swap( Type& Val1, Type& Val2 ) { + Type Temp; + + Temp = Val1; + Val1 = Val2; + Val2 = Temp; +} + +template< class Type > +class hhCycleList { + public: + hhCycleList(); + virtual ~hhCycleList(); + + void Clear(); + void Append( Type& obj ); + void AddUnique( Type& obj ); + const Type& Next(); + const Type& Previous(); + const Type& Random(); + const Type& Get() const; + int Num() const; + //rww - needed for networking sync + int GetCurrentIndex(void) { return currentIndex; } + void SetCurrentIndex(int newIndex) { currentIndex = newIndex; } + + const Type & operator[]( int index ) const; + Type & operator[]( int index ); + protected: + int currentIndex; + idList list; +}; + +/* +=============== +hhCycleList::hhCycleList +=============== +*/ +template< class Type > +ID_INLINE hhCycleList::hhCycleList() { + Clear(); +} + +/* +=============== +hhCycleList::~hhCycleList +=============== +*/ +template< class Type > +ID_INLINE hhCycleList::~hhCycleList() { + Clear(); +} + +/* +=============== +hhCycleList::Clear +=============== +*/ +template< class Type > +ID_INLINE void hhCycleList::Clear() { + list.Clear(); + currentIndex = 0; +} + +/* +=============== +hhCycleList::Append +=============== +*/ +template< class Type > +ID_INLINE void hhCycleList::Append( Type& obj ) { + list.Append( obj ); +} + +/* +=============== +hhCycleList::AddUnique +=============== +*/ +template< class Type > +ID_INLINE void hhCycleList::AddUnique( Type& obj ) { + list.AddUnique( obj ); +} + +/* +=============== +hhCycleList::Next +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Next() { + assert( list.Num() ); + + currentIndex = (currentIndex + 1) % list.Num(); + return Get(); +} + +/* +=============== +hhCycleList::Previous +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Previous() { + --currentIndex; + if( currentIndex < 0 ) { + currentIndex = (list.Num() - 1); + } + return Get(); +} + +/* +=============== +hhCycleList::Random +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Random() { + currentIndex = gameLocal.random.RandomInt( list.Num() ); + return Get(); +} + +/* +=============== +hhCycleList::Get +=============== +*/ +template< class Type > +ID_INLINE const Type& hhCycleList::Get() const { + return list[currentIndex]; +} + +/* +=============== +hhCycleList::Num +=============== +*/ +template< class Type > +ID_INLINE int hhCycleList::Num() const { + return list.Num(); +} + +/* +=============== +hhCycleList::operator[] +=============== +*/ +template< class Type > +ID_INLINE const Type & hhCycleList::operator[]( int index ) const { + return list[index]; +} + +/* +=============== +hhCycleList::operator[] +=============== +*/ +template< class Type > +ID_INLINE Type & hhCycleList::operator[]( int index ) { + return list[index]; +} + +#endif /* __GAME_UTILS_H__ */ diff --git a/neo/prey/Prey/game_vehicle.cpp b/neo/prey/Prey/game_vehicle.cpp new file mode 100644 index 00000000..6bc9ea2f --- /dev/null +++ b/neo/prey/Prey/game_vehicle.cpp @@ -0,0 +1,1922 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//========================================================================== +// +// hhVehicleThruster +// +//========================================================================== +CLASS_DECLARATION( idEntity, hhVehicleThruster ) + EVENT( EV_Broadcast_AssignFx, hhVehicleThruster::Event_AssignFxSmoke ) +END_CLASS + +hhVehicleThruster::hhVehicleThruster() { + fxSmoke = NULL; +} + +void hhVehicleThruster::Spawn() { + owner = NULL; + GetPhysics()->SetContents(0); + soundDistance = spawnArgs.GetFloat("soundDistance"); + bSomeThrusterActive = false; + bSoundMaster = spawnArgs.GetBool("soundmaster"); + localVelocity = localOffset = localDirection = vec3_origin; + fl.networkSync = true; +} + +void hhVehicleThruster::Save(idSaveGame *savefile) const { + owner.Save(savefile); + fxSmoke.Save(savefile); + savefile->WriteVec3(localOffset); + savefile->WriteVec3(localDirection); + savefile->WriteFloat(soundDistance); + savefile->WriteBool(bSomeThrusterActive); + savefile->WriteBool(bSoundMaster); + savefile->WriteVec3(localVelocity); +} + +void hhVehicleThruster::Restore( idRestoreGame *savefile ) { + owner.Restore(savefile); + fxSmoke.Restore(savefile); + savefile->ReadVec3(localOffset); + savefile->ReadVec3(localDirection); + savefile->ReadFloat(soundDistance); + savefile->ReadBool(bSomeThrusterActive); + savefile->ReadBool(bSoundMaster); + savefile->ReadVec3(localVelocity); +} + +void hhVehicleThruster::WriteToSnapshot( idBitMsgDelta &msg ) const { + WriteBindToSnapshot(msg); + GetPhysics()->WriteToSnapshot(msg); + + msg.WriteBits(owner.GetSpawnId(), 32); + msg.WriteBits(fxSmoke.GetSpawnId(), 32); + + msg.WriteFloat(localOffset.x); + msg.WriteFloat(localOffset.y); + msg.WriteFloat(localOffset.z); + + msg.WriteFloat(localDirection.x); + msg.WriteFloat(localDirection.y); + msg.WriteFloat(localDirection.z); + + msg.WriteFloat(soundDistance); + + msg.WriteBits(bSomeThrusterActive, 1); + msg.WriteBits(bSoundMaster, 1); + + msg.WriteFloat(localVelocity.x); + msg.WriteFloat(localVelocity.y); + msg.WriteFloat(localVelocity.z); + + msg.WriteBits(IsHidden(), 1); +} + +void hhVehicleThruster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ReadBindFromSnapshot(msg); + GetPhysics()->ReadFromSnapshot(msg); + + owner.SetSpawnId(msg.ReadBits(32)); + fxSmoke.SetSpawnId(msg.ReadBits(32)); + + localOffset.x = msg.ReadFloat(); + localOffset.y = msg.ReadFloat(); + localOffset.z = msg.ReadFloat(); + + localDirection.x = msg.ReadFloat(); + localDirection.y = msg.ReadFloat(); + localDirection.z = msg.ReadFloat(); + + soundDistance = msg.ReadFloat(); + + bSomeThrusterActive = !!msg.ReadBits(1); + bSoundMaster = !!msg.ReadBits(1); + + localVelocity.x = msg.ReadFloat(); + localVelocity.y = msg.ReadFloat(); + localVelocity.z = msg.ReadFloat(); + + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } else { + Show(); + } + } + + if (fxSmoke.IsValid() && fxSmoke.GetEntity() && fxSmoke->IsType(idEntityFx::Type)) { + if (fxSmoke->IsHidden() != IsHidden()) { + fxSmoke->BecomeActive( TH_THINK ); + if (IsHidden()) { + fxSmoke->Nozzle(false); + fxSmoke->fl.hidden = true; + } else { + fxSmoke->Nozzle(true); + fxSmoke->fl.hidden = false; + } + } + } +} + +void hhVehicleThruster::ClientPredictionThink( void ) { + Think(); +} + +void hhVehicleThruster::SetSmoker(bool bSmoker, idVec3 &offset, idVec3 &dir) { + localOffset = offset; + localDirection = dir; + if ( bSmoker && (!fxSmoke.IsValid() || !fxSmoke.GetEntity()) ) { + hhFxInfo fxInfo; + const char *smokeName; + smokeName = spawnArgs.GetString( "fx_smoke" ); + if (smokeName && *smokeName) { + fxInfo.SetStart(false); + fxInfo.RemoveWhenDone(false); + fxInfo.SetEntity( this ); + idVec3 loc = owner->GetOrigin() + localOffset * owner->GetAxis(); + idMat3 axis = (localDirection * owner->GetAxis()).ToMat3(); + BroadcastFxInfo( smokeName, loc, axis, &fxInfo, &EV_Broadcast_AssignFx, false ); + } + } + else if (!bSmoker && fxSmoke.IsValid() && fxSmoke.GetEntity()) { + fxSmoke->PostEventMS(&EV_Remove, 0); + fxSmoke = NULL; + } +} + +void hhVehicleThruster::SetThruster(bool on) { + + if (fxSmoke.IsValid() && fxSmoke.GetEntity()) { + fxSmoke->Nozzle( on ); + } + + on ? Show() : Hide(); +} + +void hhVehicleThruster::SetDying(bool bDying) { + if (bSoundMaster) { + SetSmoker(true, localOffset, localDirection); + } + else { + SetSmoker(bDying, localOffset, localDirection); + } +} + +void hhVehicleThruster::Update( const idVec3 &vel ) { + if (bSoundMaster) { + localVelocity = vel; + bool on = localVelocity != vec3_origin; + if (on && !bSomeThrusterActive) { + // Just became active + StartSound("snd_thrust", SND_CHANNEL_THRUSTERS); + bSomeThrusterActive = true; + } + else if (!on) { + // Just became inactive + StopSound(SND_CHANNEL_THRUSTERS); + bSomeThrusterActive = false; + } + } + +} + +bool hhVehicleThruster::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + idVec3 toSound; + idVec3 toPilot; + if (owner.IsValid()) { + axis.Identity(); + toSound = -localVelocity; // Position thrust sounds in the opposite direction as thrust velocity + toSound.Normalize(); + toPilot = (owner->GetOrigin() - GetOrigin()) + (owner->spawnArgs.GetVector("offset_pilot") + idVec3(0,0,1)*owner->spawnArgs.GetFloat("pilot_eyeHeight"))*owner->GetAxis(); + origin = toPilot + (toSound * owner->GetAxis() * soundDistance); + return true; + } + return false; + +} + +void hhVehicleThruster::Event_AssignFxSmoke( hhEntityFx* fx ) { + fxSmoke = fx; +} + +//========================================================================== +// +// hhPilotVehicleInterface +// +//========================================================================== +CLASS_DECLARATION( idClass, hhPilotVehicleInterface ) +END_CLASS + +hhPilotVehicleInterface::hhPilotVehicleInterface() { + UnderScriptControl( false ); +} + +//rww - added to handle when a player is destroyed before a vehicle (only in mp i guess) +hhPilotVehicleInterface::~hhPilotVehicleInterface() { + if (pilot.IsValid() && pilot.GetEntity()) { + pilot->SetVehicleInterface(NULL); + } + if (vehicle.IsValid() && vehicle.GetEntity()) { + vehicle->SetPilotInterface(NULL); + } +} + +void hhPilotVehicleInterface::Save(idSaveGame *savefile) const { + pilot.Save(savefile); + vehicle.Save(savefile); + savefile->WriteBool(underScriptControl); +} + +void hhPilotVehicleInterface::Restore( idRestoreGame *savefile ) { + pilot.Restore(savefile); + vehicle.Restore(savefile); + savefile->ReadBool(underScriptControl); +} + +void hhPilotVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { + if( pilot.IsValid() ) { + pilot->GetPilotInput( cmds, viewAngles ); + } +} + +void hhPilotVehicleInterface::TakeControl( hhVehicle* theVehicle, idActor* thePilot ) { + vehicle = theVehicle; + pilot = thePilot; + + if( theVehicle ) { + theVehicle->AcceptPilot( this ); + } +} + +void hhPilotVehicleInterface::ReleaseControl() { + if (vehicle.IsValid()) { + vehicle->ReleaseControl(); + } + vehicle = NULL; + pilot = NULL; +} + +idVec3 hhPilotVehicleInterface::DeterminePilotOrigin() const { + assert( vehicle.IsValid() ); + + return vehicle->DeterminePilotOrigin(); +} + +idMat3 hhPilotVehicleInterface::DeterminePilotAxis() const { + assert( vehicle.IsValid() ); + + return vehicle->DeterminePilotAxis(); +} + +bool hhPilotVehicleInterface::ControllingVehicle() const { + return vehicle.IsValid() && vehicle->IsVehicle(); +} + +hhVehicle* hhPilotVehicleInterface::GetVehicle() const { + return vehicle.GetEntity(); +} + +idActor* hhPilotVehicleInterface::GetPilot() const { + return pilot.GetEntity(); +} + +bool hhPilotVehicleInterface::InvalidVehicleImpulse( int impulse ) { + switch( impulse ) { + case IMPULSE_0: + case IMPULSE_1: + case IMPULSE_2: + case IMPULSE_3: + case IMPULSE_4: + case IMPULSE_5: + case IMPULSE_6: + case IMPULSE_7: + case IMPULSE_8: + case IMPULSE_9: + case IMPULSE_10: + case IMPULSE_11: + case IMPULSE_12: + case IMPULSE_13: + case IMPULSE_14: + case IMPULSE_15: + case IMPULSE_16: + case IMPULSE_19: + case IMPULSE_25: + case IMPULSE_40: + case IMPULSE_54: + return true; + } + + return false; +} + +//========================================================================== +// +// hhAIVehicleInterface +// +//========================================================================== +CLASS_DECLARATION( hhPilotVehicleInterface, hhAIVehicleInterface ) +END_CLASS + +hhAIVehicleInterface::hhAIVehicleInterface(void) { + stateFiring = false; + stateAltFiring = false; +} + +void hhAIVehicleInterface::Save(idSaveGame *savefile) const { + savefile->WriteUsercmd(bufferedCmds); + savefile->WriteAngles(bufferedViewAngles); + savefile->WriteBool(stateFiring); + savefile->WriteBool(stateAltFiring); + savefile->WriteFloat(stateOrientSpeed); + savefile->WriteFloat(stateThrustSpeed); + savefile->WriteVec3(stateOrientDestination); + savefile->WriteVec3(stateThrustDestination); + +} + +void hhAIVehicleInterface::Restore( idRestoreGame *savefile ) { + savefile->ReadUsercmd(bufferedCmds); + savefile->ReadAngles(bufferedViewAngles); + savefile->ReadBool(stateFiring); + savefile->ReadBool(stateAltFiring); + savefile->ReadFloat(stateOrientSpeed); + savefile->ReadFloat(stateThrustSpeed); + savefile->ReadVec3(stateOrientDestination); + savefile->ReadVec3(stateThrustDestination); +} + +void hhAIVehicleInterface::TakeControl( hhVehicle* vehicle, idActor* pilot ) { + hhPilotVehicleInterface::TakeControl( vehicle, pilot ); + + ClearBufferedCmds(); + bufferedViewAngles = (pilot) ? pilot->GetAxis().ToAngles() : ang_zero; +} + +void hhAIVehicleInterface::Fire(bool on) { + stateFiring = on; +} + +void hhAIVehicleInterface::AltFire(bool on) { + stateAltFiring = on; +} + +void hhAIVehicleInterface::OrientTowards( const idVec3 &loc, float speed ) { + stateOrientDestination = loc; + stateOrientSpeed = idMath::ClampFloat( 0.0f, 1.0f, speed ); +} + +void hhAIVehicleInterface::ThrustTowards( const idVec3 &loc, float speed ) { + stateThrustDestination = loc; + stateThrustSpeed = idMath::ClampFloat( 0.0f, 1.0f, speed ); +} + +void hhAIVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { + + if( !ControllingVehicle() ) { + return; + } + + // First grab any buffered commands + cmds = bufferedCmds; + viewAngles = bufferedViewAngles; + + idVec3 eyePos = GetPilot()->GetEyePosition(); + + // Now, Override the buffered input with any script/ai requests + // Based on current script requests, construct a command packet + cmds.buttons |= (stateFiring ? BUTTON_ATTACK : 0); + cmds.buttons |= (stateAltFiring ? BUTTON_ATTACK_ALT : 0); + + // Handle thrusting + if ( stateThrustSpeed > 0.0f ) { + idVec3 targetDir = stateThrustDestination - eyePos; + + // Use proportional control system to ease into destination + const float proportionalGain = 0.7f; + float error = idMath::ClampFloat(0.0f, 1.0f, targetDir.Normalize() / 512.0f); // error only taken into consideration when within 512 units of destination + if (error < 1.0f) { + error *= proportionalGain; + } + + targetDir *= vehicle->GetAxis().Inverse(); + + cmds.forwardmove = targetDir.x * 127.0f * error * stateThrustSpeed; + cmds.rightmove = -targetDir.y * 127.0f * error * stateThrustSpeed; + cmds.upmove = targetDir.z * 127.0f * error * stateThrustSpeed; + } + + // Handle orienting: Using direction vectors to interpolate our orientation to our moving target orientation + viewAngles.pitch = -idMath::AngleNormalize180( vehicle->GetAxis()[0].ToPitch() ); + viewAngles.yaw = vehicle->GetAxis()[0].ToYaw(); + viewAngles.roll = 0.0f; + + if ( stateOrientSpeed > 0.0f ) { + idVec3 localDir; + idAngles idealViewAngles( ang_zero ); + idVec3 targetDir = stateOrientDestination - eyePos; + targetDir.Normalize(); + float degrees = stateOrientSpeed * 360.0f * MS2SEC(gameLocal.msec); + + vehicle->GetPhysics()->GetAxis().ProjectVector( targetDir, localDir ); + idealViewAngles.yaw = localDir.ToYaw(); + idealViewAngles.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + + idAngles deltaViewAngles = (idealViewAngles - viewAngles).Normalize180(); + deltaViewAngles.Clamp( idAngles(-degrees, -degrees, 0.0f), idAngles(degrees, degrees, 0.0f) ); + if( !deltaViewAngles.Compare(ang_zero, VECTOR_EPSILON) ) { + viewAngles += deltaViewAngles; + bufferedViewAngles = viewAngles; // Save for next time + } + } +} + +void hhAIVehicleInterface::BufferPilotCmds( const usercmd_t* cmds, const idAngles* viewAngles ) { + if( cmds ) { + bufferedCmds = *cmds; + } + + if( viewAngles ) { + bufferedViewAngles = *viewAngles; + } +} + +void hhAIVehicleInterface::ClearBufferedCmds() { + memset( &bufferedCmds, 0, sizeof(usercmd_t) ); + stateFiring = false; + stateAltFiring = false; + stateOrientSpeed = 0.0f; + stateThrustSpeed = 0.0f; +} + +bool hhAIVehicleInterface::IsVehicleDocked() const { + return vehicle.IsValid() && vehicle->IsDocked(); +} + +//========================================================================== +// +// hhPlayerVehicleInterface +// +//========================================================================== +CLASS_DECLARATION( hhPilotVehicleInterface, hhPlayerVehicleInterface ) +END_CLASS + +hhPlayerVehicleInterface::hhPlayerVehicleInterface() { + hud = NULL; + uniqueHud = true; + translationAlpha.Init(gameLocal.time, 0, 0.0f, 0.0f); +} + +hhPlayerVehicleInterface::~hhPlayerVehicleInterface() { +} + +void hhPlayerVehicleInterface::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject(weaponHandState); + controlHand.Save(savefile); + savefile->WriteBool(uniqueHud); + savefile->WriteUserInterface(hud, uniqueHud); + + savefile->WriteFloat( translationAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( translationAlpha.GetDuration() ); + savefile->WriteFloat( translationAlpha.GetStartValue() ); + savefile->WriteFloat( translationAlpha.GetEndValue() ); +} + +void hhPlayerVehicleInterface::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadStaticObject(weaponHandState); + controlHand.Restore(savefile); + savefile->ReadBool(uniqueHud); + savefile->ReadUserInterface(hud); + + savefile->ReadFloat( set ); // idInterpolate + translationAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + translationAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + translationAlpha.SetStartValue(set); + savefile->ReadFloat( set ); + translationAlpha.SetEndValue( set ); +} + +void hhPlayerVehicleInterface::UpdateControlHand( const usercmd_t& cmds ) { + if( controlHand.IsValid() ) { + controlHand->UpdateControlDirection( idVec3(Sign(cmds.forwardmove), Sign(cmds.rightmove), Sign(cmds.upmove)) ); + } +} + +void hhPlayerVehicleInterface::CreateControlHand( hhPlayer* pilot, const char* handName ) { + //RemoveHand(); + + if( !handName || !handName[0] ) { + return; + } + + weaponHandState.SetPlayer( pilot ); + weaponHandState.SetWeaponTransition( 1 ); + weaponHandState.Archive( NULL, 0, handName, 1 ); + + if ( pilot->hand.IsValid() && pilot->hand->IsType( hhControlHand::Type ) ) { + controlHand = static_cast( pilot->hand.GetEntity() ); + } + else { + controlHand = NULL; + } + + //controlHand = static_cast( hhControlHand::AddHand(pilot, handName) ); +} + +void hhPlayerVehicleInterface::RemoveHand() { + if( controlHand.IsValid() ) { + //controlHand->RemoveHand(); + controlHand = NULL; + } + weaponHandState.RestoreFromArchive(); +} + +void hhPlayerVehicleInterface::RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ) { + if( pilot.IsValid() ) { + hhPilotVehicleInterface::RetrievePilotInput( cmds, viewAngles ); + UpdateControlHand( cmds ); + } +} + +void hhPlayerVehicleInterface::TakeControl( hhVehicle* vehicle, idActor* pilot ) { + hhPilotVehicleInterface::TakeControl( vehicle, pilot ); + + if( vehicle ) { + idStr hudName = vehicle->spawnArgs.GetString( "gui_hud" ); + if( hudName.Length() ) { + uniqueHud = vehicle->spawnArgs.GetBool("uniqueguihud", "1"); + hud = uiManager->FindGui( hudName.c_str(), true, uniqueHud, !uniqueHud ); + + bool isPilotLocal = false; + idPlayer *localPl = gameLocal.GetLocalPlayer(); + if (localPl) { + if (localPl->entityNumber == pilot->entityNumber || (localPl->spectating && localPl->spectator == pilot->entityNumber)) { + isPilotLocal = true; + } + } + if (isPilotLocal) { + translationAlpha.Init( gameLocal.time, 0, 0.0f, 0.0f ); + hud->Activate( true, gameLocal.GetTime() ); + pilot->fl.clientEvents = true; //rww - hack + pilot->PostEventMS( &EV_StartHudTranslation, 1000 ); + pilot->fl.clientEvents = false; //rww - hack + } + } + + if (!gameLocal.isClient) { + CreateControlHand( static_cast(pilot), vehicle->spawnArgs.GetString("def_hand") ); + } + } +} + +void hhPlayerVehicleInterface::ReleaseControl() { + if( vehicle.IsValid() ) { + hud = NULL; + uniqueHud = true; + if (!gameLocal.isClient) { + RemoveHand(); + } else { + if (controlHand.IsValid() && controlHand.GetEntity()) { + controlHand->Hide(); + } + } + } + + hhPilotVehicleInterface::ReleaseControl(); +} + +void hhPlayerVehicleInterface::DrawHUD( idUserInterface* _hud ) { + if( vehicle.IsValid() && vehicle->IsVehicle() && _hud ) { + float alpha = translationAlpha.GetCurrentValue(gameLocal.GetTime()); + _hud->SetStateFloat( "translationAlpha", alpha ); + _hud->SetStateBool( "translationProject", false ); + + vehicle->DrawHUD( _hud ); + } +} + +void hhPlayerVehicleInterface::StartHUDTranslation() { + translationAlpha.Init( gameLocal.time, 1000, 0.0f, 1.0f ); +} + + +//========================================================================== +// +// hhVehicle +// +//========================================================================== + +const idEventDef EV_VehicleExplode("", "efff"); +const idEventDef EV_Vehicle_FireCannon( "fireCannon" ); + +const idEventDef EV_VehicleGetIn("getIn", "e"); // Script Commands +const idEventDef EV_VehicleGetOut("getOut"); +const idEventDef EV_VehicleFire("fire", "d"); +const idEventDef EV_VehicleAltFire("altFire", "d"); +const idEventDef EV_VehicleOrientTowards("orientTowards", "vf"); +const idEventDef EV_VehicleStopOrientingTOwards("stopOrientingTowards"); +const idEventDef EV_VehicleThrustTowards("thrustTowards", "vf"); +const idEventDef EV_VehicleStopThrustingTOwards("stopThrustingTowards"); +const idEventDef EV_VehicleReleaseControl("releaseControl"); +const idEventDef EV_Vehicle_EjectPilot("ejectPilot"); + +ABSTRACT_DECLARATION( hhRenderEntity, hhVehicle ) + EVENT( EV_Vehicle_FireCannon, hhVehicle::Event_FireCannon ) + EVENT( EV_VehicleExplode, hhVehicle::Event_Explode ) + EVENT( EV_VehicleGetIn, hhVehicle::Event_GetIn ) + EVENT( EV_VehicleGetOut, hhVehicle::Event_GetOut ) + EVENT( EV_VehicleFire, hhVehicle::Event_Fire ) + EVENT( EV_VehicleAltFire, hhVehicle::Event_AltFire ) + EVENT( EV_VehicleOrientTowards, hhVehicle::Event_OrientTowards ) + EVENT( EV_VehicleThrustTowards, hhVehicle::Event_ThrustTowards ) + EVENT( EV_VehicleReleaseControl, hhVehicle::Event_ReleaseScriptControl ) + EVENT( EV_Vehicle_EjectPilot, hhVehicle::Event_EjectPilot ) + EVENT( EV_ResetGravity, hhVehicle::Event_ResetGravity ) +END_CLASS + +hhVehicle::~hhVehicle() { + if (GetPilotInterface()) { + idActor *actor = GetPilotInterface()->GetPilot(); + if (actor && actor->IsType(idActor::Type)) { //if it still has a valid pilot, eject them. + EjectPilot(); + } + } +} + +void hhVehicle::Spawn() { + memset( &oldCmds, 0, sizeof(usercmd_t) ); + thrusterCost = spawnArgs.GetInt( "thrusterCost" ); + currentPower = spawnArgs.GetInt( "maxPower" ); + thrustFactor = spawnArgs.GetFloat( "thrustFactor" ); + dockBoostFactor = spawnArgs.GetFloat( "dockBoostFactor" ); + if ( gameLocal.isMultiplayer ) { + noDamage = false; + } else { + noDamage = spawnArgs.GetBool( "noDamage", "0" ); + } + + bDamageSelfOnCollision = spawnArgs.GetBool( "damageSelfOnCollision" ); + bDamageOtherOnCollision = spawnArgs.GetBool( "damageOtherOnCollision" ); + + lastAttackTime = 0; + fl.neverDormant = false; + fl.refreshReactions = true; + fireController = NULL; + pilotInterface = NULL; + bHeadlightOn = false; + validThrustTime = gameLocal.time; + + bDisallowAttackUntilRelease = false; + bDisallowAltAttackUntilRelease = false; + + InitializeAttackFuncs(); + + //rww - i see no reason why the dock should be set upon spawning the vehicle. + //also would cause issues in mp where only one shuttle should be docked at a time. + //startDock is set by the dock who created us + /* + hhDock *dock = (hhDock *)gameLocal.FindEntity(spawnArgs.GetString("startDock")); + SetDock( dock ); + if (dock && dock->IsType(hhShuttleDock::Type)) { + hhShuttleDock *shDock = static_cast(dock); + if (shDock->GetDockedShuttle()) { //if the dock already has a shuttle, do not dock this ship there. + SetDock(NULL); + } + } + */ + + InitPhysics(); + + BecomeConsole(); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetInt( "nodrop" ) ) { + physicsObj.PutToRest(); + } + else { + physicsObj.DropToFloor(); + } +} + +void hhVehicle::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject(physicsObj); + savefile->WriteMat3(modelAxis); + + if( fireController ) { + savefile->WriteBool(true); + savefile->WriteStaticObject(*fireController); + } else { + savefile->WriteBool(false); + } + + savefile->WriteUsercmd(oldCmds); + + headlight.Save(savefile); + domelight.Save(savefile); + + savefile->WriteBool(bHeadlightOn); + savefile->WriteInt(currentPower); + savefile->WriteFloat(thrustFactor); + savefile->WriteFloat(thrustMin); + savefile->WriteFloat(thrustMax); + savefile->WriteFloat(thrustAccel); + savefile->WriteFloat(thrustScale); + savefile->WriteFloat(dockBoostFactor); + + dock.Save(savefile); + lastAttacker.Save(savefile); + + savefile->WriteInt(lastAttackTime); + savefile->WriteInt(vehicleClipMask); + savefile->WriteInt(vehicleContents); + savefile->WriteBool(bDamageSelfOnCollision); + savefile->WriteBool(bDamageOtherOnCollision); + savefile->WriteBool(bDisallowAttackUntilRelease); + savefile->WriteBool(bDisallowAltAttackUntilRelease); + savefile->WriteInt(thrusterCost); + savefile->WriteInt(validThrustTime); + + savefile->WriteEventDef(attackFunc); + savefile->WriteEventDef(finishedAttackingFunc); + savefile->WriteEventDef(altAttackFunc); + savefile->WriteEventDef(finishedAltAttackingFunc); + + savefile->WriteBool(noDamage); +} + +void hhVehicle::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject(physicsObj); + RestorePhysics(&physicsObj); + + savefile->ReadMat3(modelAxis); + + bool test; + savefile->ReadBool( test ); + if (test) { + fireController = CreateFireController(); + savefile->ReadStaticObject(*fireController); + } else { + SAFE_DELETE_PTR(fireController); + } + + savefile->ReadUsercmd(oldCmds); + + headlight.Restore(savefile); + domelight.Restore(savefile); + + savefile->ReadBool(bHeadlightOn); + savefile->ReadInt(currentPower); + savefile->ReadFloat(thrustFactor); + savefile->ReadFloat(thrustMin); + savefile->ReadFloat(thrustMax); + savefile->ReadFloat(thrustAccel); + savefile->ReadFloat(thrustScale); + savefile->ReadFloat(dockBoostFactor); + + dock.Restore(savefile); + lastAttacker.Restore(savefile); + + savefile->ReadInt(lastAttackTime); + savefile->ReadInt(vehicleClipMask); + savefile->ReadInt(vehicleContents); + savefile->ReadBool(bDamageSelfOnCollision); + savefile->ReadBool(bDamageOtherOnCollision); + savefile->ReadBool(bDisallowAttackUntilRelease); + savefile->ReadBool(bDisallowAltAttackUntilRelease); + savefile->ReadInt(thrusterCost); + savefile->ReadInt(validThrustTime); + + savefile->ReadEventDef(attackFunc); + savefile->ReadEventDef(finishedAttackingFunc); + savefile->ReadEventDef(altAttackFunc); + savefile->ReadEventDef(finishedAltAttackingFunc); + + savefile->ReadBool(noDamage); +} + +const idEventDef* hhVehicle::GetAttackFunc( const char* funcName ) { + function_t* function = NULL; + + if( !funcName || !funcName[0] ) { + return NULL; + } + + idTypeDef *funcType = gameLocal.program.FindType(funcName); + if (!funcType) { + return NULL; + } + + function = gameLocal.program.FindFunction( funcName, funcType ); + if( !function || !function->eventdef ) { + return NULL; + } + + HH_ASSERT( RespondsTo(*function->eventdef) ); + + return function->eventdef; +} + +void hhVehicle::InitializeAttackFuncs() { + idStr funcName; + attackFunc = finishedAttackingFunc = altAttackFunc = finishedAltAttackingFunc = NULL; + + funcName = spawnArgs.GetString("attackFunc"); + if (funcName.Length()) { + attackFunc = GetAttackFunc( funcName.c_str() ); + finishedAttackingFunc = GetAttackFunc( (funcName + "Done").c_str() ); + } + funcName = spawnArgs.GetString("altAttackFunc"); + if (funcName.Length()) { + altAttackFunc = GetAttackFunc( funcName.c_str() ); + finishedAltAttackingFunc = GetAttackFunc( (funcName + "Done").c_str() ); + } +} + +void hhVehicle::Think() { + usercmd_t pilotCmds; + idAngles pilotViewAngles; + + if( IsVehicle() && GetPilotInterface() ) { + GetPilotInterface()->RetrievePilotInput( pilotCmds, pilotViewAngles ); + ProcessPilotInput( &pilotCmds, &pilotViewAngles ); + GetPhysics()->SetAxis( mat3_identity ); + } + + hhRenderEntity::Think(); +} + +void hhVehicle::Present() { + if (fireController) { + fireController->UpdateMuzzleFlash(); + } + + hhRenderEntity::Present(); +} + +void hhVehicle::AcceptPilot( hhPilotVehicleInterface* pilotInterface ) { + //assure that the vehicle is showing upon accepting a new pilot + Show(); + + this->pilotInterface = pilotInterface; + + fl.neverDormant = true; + fl.takedamage = true; + StartSound( "snd_activation", SND_CHANNEL_ANY ); + StartSound( "snd_inuse", SND_CHANNEL_MISC1 ); + + CreateDomeLight(); + CreateHeadLight(); + BecomeActive( TH_TICKER ); + + const idDict* infoDict = NULL; + if ( GetPilot() && GetPilot()->IsType(idAI::Type) ) { + infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfoAI"), false ); + } else { + infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); + } + fireController = CreateFireController(); + HH_ASSERT( fireController ); + fireController->Init( infoDict, this, GetPilot() ); + + // supress model in player views, but allow it in mirrors and remote views + if (GetPilot()->IsType(hhPlayer::Type)) { + GetRenderEntity()->suppressSurfaceInViewID = GetPilot()->entityNumber + 1; + } + + BecomeVehicle(); +} + +void hhVehicle::RestorePilot( hhPilotVehicleInterface* pilotInterface) { + const idDict *infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); + assert( infoDict ); + fireController->SetWeaponDict( infoDict ); + this->pilotInterface = pilotInterface; +} + + +void hhVehicle::EjectPilot() { + assert( IsVehicle() ); + + SAFE_DELETE_PTR( fireController ); + + fl.takedamage = false; + fl.neverDormant = false; + StopSound( SND_CHANNEL_MISC1 ); + StartSound( "snd_deactivation", SND_CHANNEL_ANY ); + + FreeDomeLight(); + FreeHeadLight(); + BecomeInactive( TH_TICKER ); + + + if (GetPilotInterface()->GetPilot()) { //rww - make sure pilot is valid + GetPilotInterface()->GetPilot()->ExitVehicle( this ); + pilotInterface = NULL; + } + + RemoveVehicle(); +} + +idVec3 hhVehicle::DeterminePilotOrigin() const { + return GetOrigin() + spawnArgs.GetVector("offset_pilot") * GetAxis(); +} + +idMat3 hhVehicle::DeterminePilotAxis() const { + return GetAxis(); +} + +void hhVehicle::BecomeVehicle() { + SetVehiclePhysics(); + + SetVehicleModel(); +} + +bool hhVehicle::CanBecomeVehicle(idActor *pilot) { + // Check to see if shuttle will fit + idEntity *touch[ MAX_GENTITIES ]; + idBounds bounds, localBounds; + + idVec3 location = GetOrigin(); + localBounds[0] = spawnArgs.GetVector("mins"); + localBounds[1] = spawnArgs.GetVector("maxs"); + +// idBox box(localBounds, location, GetAxis()); +// gameRenderWorld->DebugBox(colorRed, box, 8000); + + idTraceModel trm; + trm.SetupBox( localBounds ); + idClipModel *clipModel = new idClipModel( trm ); + clipModel->Link(gameLocal.clip, this, 254, location, GetAxis()); + + int pilotClipMask = pilot ? pilot->GetPhysics()->GetClipMask() : 0; + pilotClipMask &= (~CONTENTS_HUNTERCLIP); // Vehicles don't collide with hunterclip so they don't get hung up on dock borders + int num = hhUtils::EntitiesTouchingClipmodel( clipModel, touch, MAX_GENTITIES, CLIPMASK_VEHICLE | pilotClipMask ); + bool blocked = false; + for (int i=0; iGetBindMaster() != pilot && (touch[i]->GetPhysics()->GetContents() & CLIPMASK_VEHICLE)) { + blocked = true; + //gameLocal.Printf("Blocked by %s\n", touch[i]->GetName()); + break; + } + } + + clipModel->Unlink(); + delete clipModel; + + return !blocked; +} + +bool hhVehicle::IsVehicle() const { + return fireController != NULL && GetPilotInterface() && GetPilotInterface()->GetPilot(); +} + +void hhVehicle::BecomeConsole() { + SetConsolePhysics(); + + if( IsDocked() ) { + //Feels like a hack. Shouldn't the dock do this or at least be in SetConsolePhysics + SetAxis( dock->GetAxis() ); + GetPhysics()->SetAxis( dock->GetAxis() ); + SetOrigin( dock->GetOrigin() + dock->spawnArgs.GetVector("offset_console") * dock->GetAxis() ); + + physicsObj.PutToRest(); + } + + SetConsoleModel(); +} + +bool hhVehicle::IsConsole() const { + return !IsVehicle(); +} + +idVec3 hhVehicle::GetPortalPoint() { + idVec3 offset = spawnArgs.GetVector("offset_pilot") + idVec3(0,0,1)*spawnArgs.GetFloat("pilot_eyeHeight"); + return GetOrigin() + offset * GetAxis(); +} + +void hhVehicle::Portalled(idEntity *portal) { + idActor *pilot = GetPilot(); + if (pilot) { + // Update the view angles of the player so next time we get input, it won't reset our angles + if (pilot->IsType(hhPlayer::Type)) { + hhPlayer* player = static_cast( pilot ); + idVec3 origin = DeterminePilotOrigin(); + idVec3 viewDir = GetAxis()[0]; + + // Don't know if all this is necessary, might be overkill, definitely need some of it though for 'shuttle through portal' + player->Unbind(); + player->SetOrientation( origin, mat3_identity, viewDir, viewDir.ToAngles() ); + player->SetUntransformedViewAxis( mat3_identity ); + player->Bind(this, true); + } + } +} + +void hhVehicle::ResetGravity() { + if( IsVehicle() ) { + physicsObj.SetGravity( spawnArgs.GetVector("activeGravity") ); + } + else { + physicsObj.SetGravity( spawnArgs.GetVector("inactiveGravity") ); + } +} + +void hhVehicle::SetAxis( const idMat3& axis ) { + modelAxis = axis; + + UpdateVisuals(); +} + +void hhVehicle::InitPhysics() { + physicsObj.SetSelf( this ); + + physicsObj.SetFriction( + spawnArgs.GetFloat("friction_linear"), + spawnArgs.GetFloat("friction_angular"), + spawnArgs.GetFloat("friction_contact") ); +} + +void hhVehicle::SetConsolePhysics() { + idTraceModel trm; + const char *clipModelName = spawnArgs.GetString( "clipmodel" ); + assert( clipModelName[0] ); + + if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { + gameLocal.Error( "hhVehicle '%s' at (%s): cannot load collision model %s\n", + name.c_str(), GetPhysics()->GetOrigin().ToString(0), clipModelName ); + return; + } + + physicsObj.SetClipModel( new idClipModel(trm), spawnArgs.GetFloat("density") ); + physicsObj.DisableImpact(); + physicsObj.SetBouncyness( 0.0f ); + physicsObj.SetContents( CONTENTS_VEHICLE ); + physicsObj.SetClipMask( CLIPMASK_VEHICLE | CONTENTS_PLAYERCLIP | CONTENTS_MONSTERCLIP ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.SetAngularVelocity( vec3_zero ); + + //Used as a cache for when noclipping + vehicleClipMask = physicsObj.GetClipMask(); + vehicleContents = physicsObj.GetContents(); + + ResetGravity(); +} + +void hhVehicle::SetVehiclePhysics() { + idBounds bounds( spawnArgs.GetVector("mins"), spawnArgs.GetVector("maxs") ); + + physicsObj.SetClipModel( new idClipModel(idTraceModel(bounds)), spawnArgs.GetFloat("density") ); + physicsObj.SetAxis( mat3_identity ); + physicsObj.EnableImpact(); + physicsObj.SetBouncyness( spawnArgs.GetFloat("bouncyness") ); + physicsObj.SetContents( CONTENTS_VEHICLE ); + + SetThrustBooster( spawnArgs.GetFloat("thrustMin"), spawnArgs.GetFloat("thrustMax"), spawnArgs.GetFloat("thrustAccel") ); + + int pilotClipMask = (GetPilotInterface() && GetPilotInterface()->GetPilot()) ? GetPilotInterface()->GetPilot()->GetPhysics()->GetClipMask() : 0; + pilotClipMask &= (~CONTENTS_HUNTERCLIP); // Vehicles don't collide with hunterclip so they don't get hung up on dock borders + physicsObj.SetClipMask( CLIPMASK_VEHICLE | pilotClipMask ); + + //Used as a cache for when noclipping + vehicleClipMask = physicsObj.GetClipMask(); + vehicleContents = physicsObj.GetContents(); + + ResetGravity(); +} + +void hhVehicle::SetConsoleModel() { + SetModel( spawnArgs.GetString("model") ); +} + +void hhVehicle::SetVehicleModel() { + SetModel( spawnArgs.GetString("model_active") ); +} + +float hhVehicle::CmdScale( const usercmd_t* cmd ) const { + float scale = 0.0f; + + if( !cmd ) { + return scale; + } + + idVec3 abscmd(abs(cmd->forwardmove), abs(cmd->rightmove), abs(cmd->upmove)); + + // Bound the cmd vector to a sphere whose radius is equal to the longest cmd axis + int desiredLength = max(max(abscmd[0], abscmd[1]), abscmd[2]); + if ( desiredLength != 0.0f ) { + float currentLength = abscmd.Length(); + scale = desiredLength / currentLength; + } + + return scale; +} + +void hhVehicle::SetThrustBooster(float minBooster, float maxBooster, float accelBooster) { + thrustMin = minBooster; + thrustMax = maxBooster; + thrustAccel = accelBooster; + + thrustScale = 0.1f; +} + +void hhVehicle::ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ) { + idVec3 impulse; + + if( viewAngles ) { + SetAxis( viewAngles->ToMat3() ); + } + + if( !cmds ) { + return; + } + + ProcessButtons( *cmds ); + + //Check vehicle here because we could have exited the vehicle in ProcessButtons + if( !IsVehicle() ) { + return; + } + + ProcessImpulses( *cmds ); + + if ( gameLocal.time >= validThrustTime ) { + impulse = GetAxis()[0] * cmds->forwardmove * thrustFactor; + impulse -= GetAxis()[1] * cmds->rightmove * thrustFactor; + impulse += GetAxis()[2] * cmds->upmove * thrustFactor; + } + else { + impulse = vec3_origin; + } + + // Apply booster to allow key taps to be very low thrust + thrustScale = idMath::ClampFloat( thrustMin, thrustMax, thrustScale * thrustAccel ); + if( impulse.LengthSqr() >= VECTOR_EPSILON ) { + FireThrusters( impulse * CmdScale(cmds) * thrustScale * (60.0f * USERCMD_ONE_OVER_HZ) ); + } else { + thrustScale = thrustMin; + } + + memcpy( &oldCmds, cmds, sizeof(usercmd_t) ); +} + +void hhVehicle::ProcessButtons( const usercmd_t& cmds ) { + + //This is needed in case we enter a dock while firing or using the tractor beam + if( (cmds.buttons & BUTTON_ATTACK) && !(oldCmds.buttons & BUTTON_ATTACK) ) { + if (IsDocked() && dock->AllowsExit() && spawnArgs.GetBool("fireToExit")) { + if (!gameLocal.isClient) { + EjectPilot(); + } + return; + } + } else if( (cmds.buttons & BUTTON_ATTACK_ALT) && !(oldCmds.buttons & BUTTON_ATTACK_ALT) ) { + if (IsDocked() && dock->AllowsExit() && spawnArgs.GetBool("altFireToExit")) { + if (!gameLocal.isClient) { + EjectPilot(); + } + return; + } + } + + if (bDisallowAttackUntilRelease) { + if (!(cmds.buttons & BUTTON_ATTACK)) { + bDisallowAttackUntilRelease = false; + } + if( oldCmds.buttons & BUTTON_ATTACK ) { + oldCmds.buttons &= ~BUTTON_ATTACK; + if( finishedAttackingFunc ) { + ProcessEvent( finishedAttackingFunc ); + } + } + } + else { + if( (cmds.buttons & BUTTON_ATTACK) ) { + if( attackFunc ) { + ProcessEvent( attackFunc ); + } + } else if( oldCmds.buttons & BUTTON_ATTACK ) { + if( finishedAttackingFunc ) { + ProcessEvent( finishedAttackingFunc ); + } + } + } + + if (bDisallowAltAttackUntilRelease) { + if (!(cmds.buttons & BUTTON_ATTACK_ALT)) { + bDisallowAltAttackUntilRelease = false; + } + if ( oldCmds.buttons & BUTTON_ATTACK_ALT ) { + oldCmds.buttons &= ~BUTTON_ATTACK_ALT; + if( finishedAltAttackingFunc ) { + ProcessEvent( finishedAltAttackingFunc ); + } + } + } + else { + if( cmds.buttons & BUTTON_ATTACK_ALT ) { + if( altAttackFunc ) { + ProcessEvent( altAttackFunc ); + } + } else if( oldCmds.buttons & BUTTON_ATTACK_ALT ) { + if( finishedAltAttackingFunc ) { + ProcessEvent( finishedAltAttackingFunc ); + } + } + } +} + +void hhVehicle::DoPlayerImpulse(int impulse) { + if ( gameLocal.isClient && GetPilot() && GetPilot()->IsType(hhPlayer::Type) ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + hhPlayer *pl = static_cast(GetPilot()); + + assert( pl->entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( impulse, 6 ); + pl->ClientSendEvent( hhPlayer::EVENT_IMPULSE, &msg ); + } + + switch (impulse) { + case IMPULSE_16: + if (!gameLocal.isClient) { + Headlight( !bHeadlightOn ); + } + break; + } +} + +void hhVehicle::ProcessImpulses( const usercmd_t& cmds ) { + if( (cmds.flags & UCF_IMPULSE_SEQUENCE) == (oldCmds.flags & UCF_IMPULSE_SEQUENCE) ) { + return; + } + + DoPlayerImpulse(cmds.impulse); //rww - seperated into its own function because of networked impulse events +} + +void hhVehicle::ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& impulse ) { + hhRenderEntity::ApplyImpulse( ent, id, point, impulse ); +} + +void hhVehicle::ApplyImpulse( const idVec3& impulse ) { + ApplyImpulse( gameLocal.world, 0, GetOrigin() + physicsObj.GetCenterOfMass(), impulse * physicsObj.GetMass() ); +} + +void hhVehicle::UpdateModel( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform(origin, axis) ) { + //HUMANHEAD: aob + GetRenderEntity()->axis = axis * GetAxis(); + GetRenderEntity()->origin = GetOrigin() + origin * GetRenderEntity()->axis; + //HUMANHEAD END + } else { + //HUMANHEAD: aob + GetRenderEntity()->axis = GetAxis(); + GetRenderEntity()->origin = GetOrigin(); + //HUMANHEAD 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 ); +} + +void hhVehicle::CreateDomeLight() { + if (spawnArgs.GetBool("domelight")) { + // This method is more oriented for flexibility + idVec3 light_offset = spawnArgs.GetVector("offset_domelight"); + const char *objName = spawnArgs.GetString("def_domelight"); + + idDict args; + idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); + args.SetVector( "origin", lightOrigin ); + + domelight = (idLight *)gameLocal.SpawnObject(objName, &args); + domelight->Bind( this, true ); + domelight->SetLightParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); + } +} + +void hhVehicle::FreeDomeLight() { + SAFE_REMOVE( domelight ); +} + +void hhVehicle::CreateHeadLight() { + if ( spawnArgs.GetBool("headlight") ) { + // This method is more oriented for presenting our own lights + // Depending on the speed cost of having seperate entities for lights, we may + // want to present our own, like the weapons + idStr light_shader = spawnArgs.GetString("mtr_headlight"); + idVec3 light_color = spawnArgs.GetVector("headlight_color"); + idVec3 light_offset = spawnArgs.GetVector("offset_headlight"); + idVec3 light_target = spawnArgs.GetVector("offset_headlighttarget"); + idVec3 light_frustum = spawnArgs.GetVector("headlight_frustum"); + + idDict args; + idVec3 lightOrigin = GetPhysics()->GetOrigin() + light_offset * GetPhysics()->GetAxis(); + light_target.Normalize(); + idMat3 lightAxis = (light_target * GetPhysics()->GetAxis()).hhToMat3(); + + if ( light_shader.Length() ) { + args.Set( "texture", light_shader ); + } + args.SetVector( "origin", lightOrigin ); + args.Set ("angles", lightAxis.ToAngles().ToString()); + args.SetVector( "_color", light_color ); + args.SetVector( "light_target", lightAxis[0] * light_frustum.x ); + args.SetVector( "light_right", lightAxis[1] * light_frustum.y ); + args.SetVector( "light_up", lightAxis[2] * light_frustum.z ); + headlight = ( idLight * )gameLocal.SpawnEntityTypeClient( idLight::Type, &args ); + + //rww - headlight is a pure local entity + assert(headlight.IsValid() && headlight.GetEntity()); + headlight->fl.networkSync = false; + headlight->fl.clientEvents = true; + + headlight->Bind(this, true); + + headlight->SetLightParm( 6, 0.0f ); // fade out + headlight->SetLightParm( SHADERPARM_TIMEOFFSET, 0 ); // Initially faded out already + } +} + +void hhVehicle::FreeHeadLight() { + SAFE_REMOVE( headlight ); +} + +void hhVehicle::Headlight( bool on ) { + bHeadlightOn = on; + float timeOffset = -MS2SEC(gameLocal.time); + + if( headlight.IsValid() ) { + headlight->SetLightParm(SHADERPARM_TIMEOFFSET, timeOffset); + SetShaderParm(7, timeOffset); + + if (bHeadlightOn) { + headlight->SetLightParm(6, 1.0f); // Fade light in + SetShaderParm(6, 1.0f); // Fade light cone in + } + else { + headlight->SetLightParm(6, 0.0f); // Fade out + SetShaderParm(6, 0.0f); // Fade out + } + } +} + +void hhVehicle::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if (InGodMode() || InDialogMode()) { + return; + } + + if (gameLocal.isMultiplayer) { //rww - don't let shuttles damage themselves or their teammates + if (attacker && attacker->IsType(hhPlayer::Type) && GetPilot() == attacker) { + return; + } + + hhPlayer *playerPilot = (GetPilot() && GetPilot()->IsType( hhPlayer::Type )) ? static_cast(GetPilot()) : NULL; + hhPlayer *player = (attacker && attacker->IsType( hhPlayer::Type )) ? static_cast(attacker) : NULL; + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( (gameLocal.gameType == GAME_TDM + && playerPilot + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && damageDef + && !damageDef->GetBool( "noTeam" ) + && player + && player->team == playerPilot->team) ) { + return; + } + } else { + if ( noDamage ) { + return; + } + if (attacker && attacker->IsType(idAI::Type) && GetPilot() == attacker) { + return; + } + } + + if (GetPilotInterface() && GetPilotInterface()->GetPilot()) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + + if (gameLocal.isMultiplayer && IsType(hhShuttle::Type)) { //rww - in mp, let's try flickering the tractor beam on and off when hit + hhShuttle *shtlSelf = static_cast(this); + if (shtlSelf->noTractorTime <= gameLocal.time && shtlSelf->TractorIsActive()) { //don't let it stay off for a long time from constant attack + float dmg = ((float)damageDef->GetInt("damage", "100"))*damageScale; + int dmgTime = (int)(dmg*12); + //cap it to something reasonable on both ends + if (dmgTime < 100) { + dmgTime = 100; + } + else if (dmgTime > 1200) { + dmgTime = 1200; + } + shtlSelf->noTractorTime = gameLocal.time + dmgTime; + } + } + + // Let playerview know about the impact direction + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + idVec3 damage_from, localDamageVector; + damage_from = dir; + damage_from.Normalize(); + if ( GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type)) { + // Pass this on so we can get directional damage + hhPlayer *playerPilot = static_cast(GetPilotInterface()->GetPilot()); + playerPilot->viewAxis.ProjectVector( damage_from, localDamageVector ); + playerPilot->playerView.DamageImpulse( localDamageVector, damageDef ); + + // Track last attacker for use in displaying HUD hit indicator + if (!gameLocal.isClient) { + playerPilot->ReportAttack(attacker); + } + + if (gameLocal.isMultiplayer) { //rww - in MP, we want to damage the player a little from shuttle damage + float plDmgScale = damageScale*0.2f; + bool pilotWasTakingDamage = playerPilot->fl.takedamage; + playerPilot->fl.takedamage = true; + playerPilot->Damage(inflictor, attacker, dir, damageDefName, plDmgScale, INVALID_JOINT); + playerPilot->fl.takedamage = pilotWasTakingDamage; + if (playerPilot->health == 1) { //if the player died (or almost, kind of a hack) as a result, blow me up too + damageDefName = "damage_suicide"; + } + } + } + } + + // Tell HUD to show impact direction + if( IsVehicle() ) {//AI may want to know who hit them + lastAttacker = attacker; + lastAttackTime = gameLocal.time; + } + idEntity::Damage( inflictor, attacker, dir, damageDefName, 1.0f, location ); +} + +void hhVehicle::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (gameLocal.isClient) { + return; + } + if( gameLocal.isMultiplayer || health < spawnArgs.GetInt("gibhealth") ) { //rww - always explode in mp + CancelEvents(&EV_VehicleExplode); + PostEventMS(&EV_VehicleExplode, 0, attacker, dir.x, dir.y, dir.z); + } + else if( health + damage > 0 ) { // Only do this the first death + ConsumePower(currentPower); + + if( domelight.IsValid() ) { + domelight->SetLightParm(7, 1.0f); + } + + StopSound( SND_CHANNEL_DYING, true ); + StartSound( "snd_dying", SND_CHANNEL_DYING, 0, true ); + + // control release during the damage pipe was causing a crash when damaged by splash damage + // the clip model is invalidated, then radius damage tries to apply to it. + // Should be okay to switch back if "killed" is made into an event + CancelEvents( &EV_VehicleExplode ); + PostEventSec( &EV_VehicleExplode, spawnArgs.GetFloat("explodedelay"), attacker, dir.x, dir.y, dir.z ); + } +} + +bool hhVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { + static const float minCollisionVelocity = 40.0f; + static const float maxCollisionVelocity = 650.0f; + + // Velocity in normal direction + float len = velocity * -collision.c.normal; + + if ( len > minCollisionVelocity && collision.c.material && !(collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + if (StartSound( "snd_bounce", SND_CHANNEL_BODY3 )) { + // Change volume only after we know the sound played + float volume = hhUtils::CalculateSoundVolume( len, minCollisionVelocity, maxCollisionVelocity ); + HH_SetSoundVolume(volume, SND_CHANNEL_BODY3); + } + + if (len > 200.0f) { + // Damage other + idEntity *other = gameLocal.GetTraceEntity(collision); + if (other && bDamageOtherOnCollision) { + idEntity *collideAttacker = this; + + if (GetPilotInterface() && GetPilotInterface()->GetPilot()) { //use pilot as attacker if we have one + collideAttacker = GetPilotInterface()->GetPilot(); + } + float damageScale = hhUtils::CalculateScale(len, 200, 600); + damageScale = (damageScale * 4.0f) + 1.0f; // damageScale in range [1..5] + damageScale *= other->spawnArgs.GetFloat("vehicledamagescale"); // for fine tuning + + // NOTE: Assuming damage_vehicleCollision is set to 20! + if (20 * damageScale >= other->health) { + other->Damage(this, collideAttacker, vec3_origin, "damage_vehicleCollision_gib", damageScale, 0); + } + else { + other->Damage(this, collideAttacker, vec3_origin, "damage_vehicleCollision", damageScale, 0); + } + } + + // Damage self + if (bDamageSelfOnCollision) { + Damage(other, other, collision.c.normal, "damage_vehicleCollision", 1.0f, 0); + } + } + } + + return false; +} + +bool hhVehicle::IsNoClipping() const { + return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->noclip; +} + +bool hhVehicle::InGodMode() const { + return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->godmode; +} + +bool hhVehicle::InDialogMode() const { + return GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type) && static_cast(GetPilotInterface()->GetPilot())->InDialogDamageMode(); +} + +void hhVehicle::GiveHealth( int amount ) { + health = idMath::ClampInt( 0, spawnHealth, health + amount ); +} + +void hhVehicle::GivePower( int amount ) { + currentPower = idMath::ClampInt( 0, spawnArgs.GetInt("maxPower"), currentPower + amount ); +} + +bool hhVehicle::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + if( !src->ReadToken(&token) || token == ";" ) { + return false; + } + if( !token.Icmp("controlvehicle") ) { + if (IsHidden()) { //rww - was possible to get back in a vehicle once it had been hidden for removal + return true; + } + if( entityGui->IsType(idActor::Type) ) { + static_cast( entityGui )->EnterVehicle( this ); + } + return true; + } + return false; +} + +void hhVehicle::DrawHUD( idUserInterface* _hud ) { + if( _hud ) { + //rww - transfer necessary hud variables from the player hud to the vehicle hud + if (GetPilotInterface() && GetPilotInterface()->GetPilot() && GetPilotInterface()->GetPilot()->IsType(hhPlayer::Type)) { + hhPlayer *plPilot = static_cast(GetPilotInterface()->GetPilot()); + if (plPilot->hud) { + //aiming + _hud->SetStateFloat("aim_R", plPilot->hud->GetStateFloat("aim_R", "")); + _hud->SetStateFloat("aim_G", plPilot->hud->GetStateFloat("aim_G", "")); + _hud->SetStateFloat("aim_B", plPilot->hud->GetStateFloat("aim_B", "")); + _hud->SetStateString("aim_text", plPilot->hud->GetStateString("aim_text", "")); + //set in UpdateHud: + /* + _hud->SetStateString("playername", plPilot->hud->GetStateString("playername", "")); + _hud->SetStateFloat("team_R", plPilot->hud->GetStateFloat("team_R", "")); + _hud->SetStateFloat("team_G", plPilot->hud->GetStateFloat("team_G", "")); + _hud->SetStateFloat("team_B", plPilot->hud->GetStateFloat("team_B", "")); + _hud->SetStateBool("ismultiplayer", plPilot->hud->GetStateBool("ismultiplayer", "")); + */ + + //set in UpdateHud: + /* + //top 4 + for (int i = 0; i < 4; i++) { + _hud->SetStateString( va( "player%i", i+1 ), plPilot->hud->GetStateString( va( "player%i", i+1 ), "")); + _hud->SetStateString( va( "player%i_portrait", i+1 ), plPilot->hud->GetStateString(va( "player%i_portrait", i+1 ), "")); + _hud->SetStateString( va( "player%i_score", i+1 ), plPilot->hud->GetStateString( va( "player%i_score", i+1 ), "")); + _hud->SetStateString( va( "rank%i", i+1 ), plPilot->hud->GetStateString( va( "rank%i", i+1 ), "")); + } + _hud->SetStateString("rank_self", plPilot->hud->GetStateString("rank_self", "")); + */ + } + } + _hud->SetStateBool( "dying", health <= 0 ); + _hud->SetStateFloat( "healthfraction", ((float)health)/(float)spawnHealth ); + _hud->SetStateFloat( "powerfraction", (currentPower)/spawnArgs.GetFloat("maxPower") ); + idAngles angles = GetAxis().ToAngles(); + _hud->SetStateFloat( "pitch", angles.pitch ); + _hud->SetStateFloat( "yaw", angles.yaw ); + _hud->SetStateFloat( "roll", angles.roll ); + _hud->Redraw( gameLocal.realClientTime ); + } +} + +void hhVehicle::PerformDeathAction(int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir) { + switch(deathAction) { + case 0: // Drop pilot + break; + case 1: // Kill pilot + savedPilot->Damage(this, attacker, dir, spawnArgs.GetString("def_killpilotdamage"), 1.0f, 0); + break; + } +} + +void hhVehicle::Explode( idEntity *attacker, idVec3 dir ) { + if (gameLocal.isClient) { + return; + } + if (!GetPilotInterface()) { + return; + } + + idEntityPtr savedPilot = GetPilotInterface()->GetPilot(); + + EjectPilot(); + + if (savedPilot.IsValid()) { + int deathAction = savedPilot->IsType(hhPlayer::Type) ? spawnArgs.GetInt("DeathActionPlayer") : spawnArgs.GetInt("DeathActionAI"); + PerformDeathAction(deathAction, savedPilot.GetEntity(), attacker, dir); + } + + const char *deathExplosionDef = spawnArgs.GetString("def_deathexplosion", ""); + if (deathExplosionDef && deathExplosionDef[0]) { + gameLocal.RadiusDamage(GetOrigin(), this, this, this, this, deathExplosionDef); + } + + // Explode into gibs + idVec3 vel = dir * 300; + hhUtils::SpawnDebrisMass(spawnArgs.GetString("def_debrisspawner"), + GetPhysics()->GetOrigin(), NULL, &vel, 1); + StopSound(SND_CHANNEL_DYING, true); + StartSound("snd_death", SND_CHANNEL_ANY); +} + +bool hhVehicle::HasPower( int amount ) const { + return currentPower >= amount; +} + +bool hhVehicle::ConsumePower( int amount ) { + if( InGodMode() ) { + return true; + } + + if (currentPower >= amount) { + currentPower -= amount; + return true; + } + + currentPower = 0; + return false; +} + +void hhVehicle::RemoveVehicle() { + //rww - stop sounds too + StopSound(SND_CHANNEL_THRUSTERS, true); + StopSound(SND_CHANNEL_MISC1, true); + StopSound(SND_CHANNEL_DYING, true); + + Hide(); + GetPhysics()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); + PostEventMS( &EV_Remove, 3000 ); // Give anything targetting it a chance to retarget +} + +void hhVehicle::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + assert(currentPower < (1<<20)); + msg.WriteBits(currentPower, 20); + assert(health < (1<<12)); + msg.WriteBits(health, 12); + msg.WriteBits(bHeadlightOn, 1); + + idCQuat modelQuat = modelAxis.ToCQuat(); + msg.WriteFloat(modelQuat.x); + msg.WriteFloat(modelQuat.y); + msg.WriteFloat(modelQuat.z); + + msg.WriteBits(renderEntity.suppressSurfaceInViewID, GENTITYNUM_BITS); + + /* + msg.WriteBits(currentPower, 32); + msg.WriteFloat(thrustFactor); + msg.WriteFloat(thrustMin); + msg.WriteFloat(thrustMax); + msg.WriteFloat(thrustAccel); + msg.WriteFloat(thrustScale); + msg.WriteFloat(dockBoostFactor); + */ + + msg.WriteBits(IsNoClipping() ? 0 : vehicleClipMask, 32); + msg.WriteBits(IsNoClipping() ? 0 : vehicleContents, 32); + msg.WriteBits(IsHidden(), 1); + + msg.WriteBits(dock.GetSpawnId(), 32); + msg.WriteBits(domelight.GetSpawnId(), 32); + WriteBindToSnapshot( msg ); + + if (fireController) { //fire controller can be null at this point. + msg.WriteBits(fireController->barrelOffsets.GetCurrentIndex(), 8); + } + else { + msg.WriteBits(0, 8); + } +} + +void hhVehicle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + currentPower = msg.ReadBits(20); + health = msg.ReadBits(12); + bool headlightOn = !!msg.ReadBits(1); + if (headlightOn != bHeadlightOn) { + Headlight(headlightOn); + } + + idCQuat modelQuat; + modelQuat.x = msg.ReadFloat(); + modelQuat.y = msg.ReadFloat(); + modelQuat.z = msg.ReadFloat(); + modelAxis = modelQuat.ToMat3(); + + renderEntity.suppressSurfaceInViewID = msg.ReadBits(GENTITYNUM_BITS); + + /* + currentPower = msg.ReadBits(32); + thrustFactor = msg.ReadFloat(); + thrustMin = msg.ReadFloat(); + thrustMax = msg.ReadFloat(); + thrustAccel = msg.ReadFloat(); + thrustScale = msg.ReadFloat(); + dockBoostFactor = msg.ReadFloat(); + */ + + int newVehicleClipMask = msg.ReadBits(32); + if (newVehicleClipMask != vehicleClipMask) { + physicsObj.SetClipMask(newVehicleClipMask); + vehicleClipMask = newVehicleClipMask; + } + int newVehicleContents = msg.ReadBits(32); + if (newVehicleContents != vehicleContents) { + physicsObj.SetContents(newVehicleContents); + vehicleContents = newVehicleContents; + } + + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } else { + Show(); + } + } + + int spawnId; + + spawnId = msg.ReadBits(32); //rwwFIXME why is 0 check needed? something checking the container strangely? + if (!spawnId) { + dock = NULL; + } + else { + dock.SetSpawnId(spawnId); + } + spawnId = msg.ReadBits(32); + if (!spawnId) { + domelight = NULL; + } + else { + if (domelight.SetSpawnId(spawnId)) { + domelight->Bind( this, true ); + domelight->SetLightParm(SHADERPARM_TIMEOFFSET, -MS2SEC(gameLocal.time)); + } + } + ReadBindFromSnapshot( msg ); + + int barrelIndex = msg.ReadBits(8); + if (fireController) { + fireController->barrelOffsets.SetCurrentIndex(barrelIndex); + } +} + +void hhVehicle::ClientPredictionThink( void ) { + Think(); +} + +bool hhVehicle::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch ( event ) { + case EVENT_EJECT_PILOT: + EjectPilot(); + return true; + default: + return hhRenderEntity::ClientReceiveEvent( event, time, msg ); + } +} + +void hhVehicle::Event_Explode( idEntity *attacker, float dx, float dy, float dz ) { + if (InDialogMode()) { + // Death not allowed, repost + CancelEvents(&EV_VehicleExplode); + PostEventMS(&EV_VehicleExplode, 1000, attacker, dx, dy, dz); + return; + } + + idVec3 dir(dx, dy, dz); + Explode(attacker, dir); +} + +void hhVehicle::Event_ResetGravity() { + ResetGravity(); +} + +void hhVehicle::Event_FireCannon() { + if( fireController && fireController->LaunchProjectiles(vec3_zero) ) { + StartSound( "snd_cannon", SND_CHANNEL_ANY ); + fireController->MuzzleFlash(); + if (GetPilot() && GetPilot()->IsType(hhPlayer::Type)) { + // Only players get weapon fire feedback, so monster shuttles aren't pushed back into docks + fireController->WeaponFeedback(); + } + } +} + +// Script control interfaces +void hhVehicle::Event_GetIn( idEntity *ent ) { + if (ent->IsType(idActor::Type)) { + static_cast( ent )->EnterVehicle( this ); + GetPilotInterface()->UnderScriptControl( true ); + } +} + +void hhVehicle::Event_GetOut() { + if (GetPilotInterface()) { + GetPilotInterface()->UnderScriptControl( true ); + EjectPilot(); + } +} + +void hhVehicle::Event_Fire( bool start ) { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->Fire( start ); + } +} + +void hhVehicle::Event_AltFire( bool start ) { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->AltFire( start ); + } +} + +void hhVehicle::Event_OrientTowards( idVec3 &point, float speed ) { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->OrientTowards( point, speed ); // passed as percentage of max + } +} + +void hhVehicle::Event_StopOrientingTowards() { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->OrientTowards( vec3_origin, 0.0f ); + } +} + +void hhVehicle::Event_ThrustTowards( idVec3 &point, float speed ) { + idVec3 pt = point; + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->ThrustTowards( pt, speed ); // passed as percentage of max + } +} + +void hhVehicle::Event_StopThrustingTowards() { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( true ); + GetPilotInterface()->ThrustTowards( vec3_origin, 0.0f ); + } +} + +void hhVehicle::Event_ReleaseScriptControl() { + if( GetPilotInterface() ) { + GetPilotInterface()->UnderScriptControl( false ); + GetPilotInterface()->ClearBufferedCmds(); + } +} + +void hhVehicle::Event_EjectPilot() { + EjectPilot(); +} + diff --git a/neo/prey/Prey/game_vehicle.h b/neo/prey/Prey/game_vehicle.h new file mode 100644 index 00000000..e05936df --- /dev/null +++ b/neo/prey/Prey/game_vehicle.h @@ -0,0 +1,375 @@ +#ifndef __GAME_VEHICLE_H__ +#define __GAME_VEHICLE_H__ + +#define CONTENTS_VEHICLE (CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_MOVEABLECLIP) +#define CLIPMASK_VEHICLE (CONTENTS_BODY|CONTENTS_VEHICLECLIP|CONTENTS_SOLID|CONTENTS_FORCEFIELD) + +extern const idEventDef EV_Vehicle_FireCannon; +extern const idEventDef EV_VehicleExplode; + +class idLight; +class hhVehicle; +class hhPlayer; +class hhDock; +class hhShuttleDock; + +enum EFireMode{ + FIREMODE_NOTHING=0, + FIREMODE_CANNON, + FIREMODE_TRACTOR, + FIREMODE_EXIT, + NUM_FIREMODES +}; + +// Support for direction masks, as used for thrusters +enum { + // The order of these is important and is laid out as: + THRUSTER_FRONT=0, // x < 0 + THRUSTER_BACK, // x > 0 + THRUSTER_LEFT, // y < 0 + THRUSTER_RIGHT, // y > 0 + THRUSTER_TOP, // z < 0 + THRUSTER_BOTTOM, // z > 0 + THRUSTER_DIRECTIONS +}; + + + +//========================================================================== +// +// hhVehicleThruster +// +//========================================================================== + +class hhVehicleThruster : public idEntity { + CLASS_PROTOTYPE( hhVehicleThruster ); +public: + hhVehicleThruster(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + void SetOwner(hhVehicle *v) { owner = v; } + void SetSmoker(bool s, idVec3 &offset, idVec3 &dir); + void SetThruster(bool on); + void Update( const idVec3 &vel ); + void SetDying(bool dying); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +protected: + void Event_AssignFxSmoke( hhEntityFx* fx ); + +protected: + //rww - changed to idEntityPtr's + idEntityPtr owner; + idEntityPtr fxSmoke; + idVec3 localOffset; + idVec3 localDirection; + float soundDistance; + bool bSomeThrusterActive; + bool bSoundMaster; + idVec3 localVelocity; +}; + + +//========================================================================== +// +// hhPilotVehicleInterface +// +// +//========================================================================== +class hhPilotVehicleInterface : public idClass { + CLASS_PROTOTYPE( hhPilotVehicleInterface ); + + public: + hhPilotVehicleInterface(); + ~hhPilotVehicleInterface(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Called from vehicle + virtual void RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ); + virtual idActor* GetPilot() const; + virtual void UnderScriptControl( bool yes ) { underScriptControl = yes; } + virtual bool UnderScriptControl() const { return underScriptControl; } + virtual void OrientTowards( const idVec3 &loc, float speed ) {} + virtual void ThrustTowards( const idVec3 &loc, float speed ) {} + virtual void Fire(bool on) {} + virtual void AltFire(bool on) {} + virtual void ClearBufferedCmds() {} + + //Called from pilot + virtual bool ControllingVehicle() const; + virtual void ReleaseControl(); + virtual hhVehicle* GetVehicle() const; + virtual void TakeControl( hhVehicle* vehicle, idActor* pilot ); + virtual idVec3 DeterminePilotOrigin() const; + virtual idMat3 DeterminePilotAxis() const; + + virtual bool InvalidVehicleImpulse( int impulse ); + int GetVehicleSpawnId() const { return vehicle.GetSpawnId(); } + bool SetVehicleSpawnId( int id ) { return vehicle.SetSpawnId( id ); } + protected: + idEntityPtr pilot; + idEntityPtr vehicle; + bool underScriptControl; +}; + +class hhAIVehicleInterface : public hhPilotVehicleInterface { + CLASS_PROTOTYPE( hhAIVehicleInterface ); + + public: + hhAIVehicleInterface(void); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Called from pilot + void BufferPilotCmds( const usercmd_t* cmds, const idAngles* viewAngles ); + virtual void OrientTowards( const idVec3 &loc, float speed ); + virtual void ThrustTowards( const idVec3 &loc, float speed ); + virtual bool IsVehicleDocked() const; + virtual void TakeControl( hhVehicle* vehicle, idActor* pilot ); + virtual void Fire(bool on); + virtual void AltFire(bool on); + + //Called from vehicle + virtual void RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ); + + protected: + void ClearBufferedCmds(); + + protected: + usercmd_t bufferedCmds; + idAngles bufferedViewAngles; + bool stateFiring; + bool stateAltFiring; + idVec3 stateOrientDestination; + float stateOrientSpeed; + idVec3 stateThrustDestination; + float stateThrustSpeed; +}; + +class hhPlayerVehicleInterface : public hhPilotVehicleInterface { + CLASS_PROTOTYPE( hhPlayerVehicleInterface ); + + public: + hhPlayerVehicleInterface(); + virtual ~hhPlayerVehicleInterface(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //Called from vehicle + virtual void RetrievePilotInput( usercmd_t& cmds, idAngles& viewAngles ); + + //Called from pilot + virtual void ReleaseControl(); + virtual void TakeControl( hhVehicle* vehicle, idActor* pilot ); + void StartHUDTranslation(); // HUMANHEAD mdl: Moved these into hhPlayer events to prevent loadgame crash + // (events must be on entities, not just idClasses, in order to save properly) + int GetHandSpawnId() const { return controlHand.GetSpawnId(); } + bool SetHandSpawnId( int id ) { return controlHand.SetSpawnId( id ); } + hhControlHand *GetHandEntity() const { return controlHand.GetEntity(); } + const hhWeaponHandState *GetWeaponHandState() const { return &weaponHandState; } + hhWeaponHandState *GetWeaponHandState() { return &weaponHandState; } + + //Called from playerView + virtual idUserInterface* GetHUD() const { return hud; } + virtual void DrawHUD( idUserInterface* _hud ); + + protected: + void UpdateControlHand( const usercmd_t& cmds ); + void CreateControlHand( hhPlayer* pilot, const char* handName ); + void RemoveHand(); + + protected: + hhWeaponHandState weaponHandState; + idEntityPtr controlHand; + + idUserInterface* hud; + bool uniqueHud; //rww + idInterpolate translationAlpha; // interpolator for hud translation +}; + +//========================================================================== +// +// hhVehicle +// +//========================================================================== + +class hhVehicle : public hhRenderEntity { + ABSTRACT_PROTOTYPE( hhVehicle ); + +public: + hhVehicle() : fireController(NULL), pilotInterface(NULL) {} + ~hhVehicle(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think(); + virtual void ClientPredictionThink( void ); + virtual void Present(); + enum { + EVENT_EJECT_PILOT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + 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 bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + virtual const idMat3& GetAxis( int id = 0 ) const { return modelAxis; } + virtual void SetAxis( const idMat3& axis ); + virtual void Portalled(idEntity *portal); + virtual idVec3 GetPortalPoint( void ); + virtual idVec3 DeterminePilotOrigin() const; + virtual idMat3 DeterminePilotAxis() const; + + virtual void FireThrusters( const idVec3& impulse ) {} + virtual void ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& impulse ); + virtual void ApplyImpulse( const idVec3& impulse ); + + virtual void SetDock( const hhDock* dock ) { this->dock = dock; } + virtual void Undock() { dock.Clear(); } + virtual bool IsDocked() const { return dock.IsValid(); } + virtual idActor* GetPilot() const { return (GetPilotInterface()) ? GetPilotInterface()->GetPilot() : NULL; } + virtual hhPilotVehicleInterface* GetPilotInterface() const { return pilotInterface; } + virtual void SetPilotInterface(hhPilotVehicleInterface *pInterface) { pilotInterface = pInterface; } + + bool IsNoClipping() const; + bool InGodMode() const; + bool InDialogMode() const; + + virtual float GetThrustFactor() const { return thrustFactor; } + + void GiveHealth( int amount ); + void GivePower( int amount ); + bool ConsumePower( int amount ); + bool HasPower( int amount ) const; + bool NeedsPower() const { return currentPower < spawnArgs.GetFloat("maxPower"); } + + virtual bool WillAcceptPilot( idActor *act ) = 0; + virtual void AcceptPilot( hhPilotVehicleInterface* pilotInterface ); + void RestorePilot( hhPilotVehicleInterface* pilotInterface); + virtual void ReleaseControl() {} + virtual void EjectPilot(); + virtual bool CanBecomeVehicle(idActor *pilot); + virtual void BecomeVehicle(); + virtual void BecomeConsole(); + virtual bool IsVehicle() const; + virtual bool IsConsole() const; + + virtual idVec3 GetFireOrigin() { return GetOrigin(); } // Called by firecontroller + virtual idMat3 GetFireAxis() { return GetAxis(); } + + virtual void ResetGravity(); + + virtual void ProcessPilotInput( const usercmd_t* cmds, const idAngles* viewAngles ); + + virtual float GetWeaponRecoil() const { return (fireController) ? fireController->GetRecoil() : 0.0f; } + virtual float GetWeaponFireDelay() const { return (fireController) ? fireController->GetFireDelay() : 0.0f; } + + virtual void DrawHUD( idUserInterface* _hud ); + + virtual void DoPlayerImpulse(int impulse); //rww - seperated into its own function because of networked impulse events + +protected: + virtual void UpdateModel(); + virtual const idEventDef* GetAttackFunc( const char* funcName ); + virtual void InitializeAttackFuncs(); + + virtual void InitPhysics(); + virtual void SetConsolePhysics(); + virtual void SetVehiclePhysics(); + + virtual void SetConsoleModel(); + virtual void SetVehicleModel(); + + void ProcessButtons( const usercmd_t& cmds ); + void ProcessImpulses( const usercmd_t& cmds ); + float CmdScale( const usercmd_t* cmd ) const; + void SetThrustBooster( float minBooster, float maxBooster, float accelBooster ); + + hhVehicleFireController * CreateFireController() { return new hhVehicleFireController; } + + virtual void CreateHeadLight(); + virtual void FreeHeadLight(); + virtual void Headlight( bool on ); + virtual void CreateDomeLight(); + virtual void FreeDomeLight(); + + virtual void RemoveVehicle(); + virtual void PerformDeathAction( int deathAction, idActor *savedPilot, idEntity *attacker, idVec3 &dir ); + virtual void Explode( idEntity *attacker, idVec3 dir ); + virtual void Event_FireCannon(); + void Event_Explode( idEntity *attacker, float dx, float dy, float dz ); + void Event_GetIn( idEntity *ent ); + void Event_GetOut(); + void Event_ResetGravity(); + void Event_Fire( bool start ); + void Event_AltFire( bool start ); + void Event_OrientTowards( idVec3 &point, float speed ); + void Event_StopOrientingTowards(); + void Event_ThrustTowards( idVec3 &point, float speed ); + void Event_StopThrustingTowards(); + void Event_ReleaseScriptControl(); + void Event_EjectPilot(); + + +protected: + hhPhysics_Vehicle physicsObj; // physics object for vehicle + idMat3 modelAxis; + + hhPilotVehicleInterface* pilotInterface; + + hhVehicleFireController* fireController; + usercmd_t oldCmds; + + //FIXME: make these renderLight structs + idEntityPtr headlight; // Head light + idEntityPtr domelight; // Dome light + bool bHeadlightOn; + + int currentPower; + float thrustFactor; + float thrustMin; + float thrustMax; + float thrustAccel; + float thrustScale; + float dockBoostFactor; + + idEntityPtr dock; + + idEntityPtr lastAttacker; + int lastAttackTime; + + int vehicleClipMask; // clipmask of vehicle when not noclipping + int vehicleContents; // contents of vehicle when not noclipping + + bool bDamageSelfOnCollision; // Vehicle takes damage when colliding + bool bDamageOtherOnCollision;// Vehicle takes damage when colliding + + bool bDisallowAttackUntilRelease; + bool bDisallowAltAttackUntilRelease; + + int thrusterCost; + int validThrustTime; // Delay thrust capability a couple seconds after entering vehicle + + const idEventDef* attackFunc; + const idEventDef* finishedAttackingFunc; + const idEventDef* altAttackFunc; + const idEventDef* finishedAltAttackingFunc; + + bool noDamage; +}; + +#endif + diff --git a/neo/prey/Prey/game_vomiter.cpp b/neo/prey/Prey/game_vomiter.cpp new file mode 100644 index 00000000..aa8ab155 --- /dev/null +++ b/neo/prey/Prey/game_vomiter.cpp @@ -0,0 +1,333 @@ +// Game_Vomiter.cpp +// +// spews vomit chunks periodically +// triggering will cause an eruption + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_Erupt("erupt", "d"); +const idEventDef EV_UnErupt("unerupt", NULL); +const idEventDef EV_DamagePulse("damagepulse", NULL); +const idEventDef EV_MovablePulse(""); + +const idEventDef EV_Broadcast_AssignFxErupt( "", "e" ); + +CLASS_DECLARATION(hhAnimatedEntity, hhVomiter) + EVENT( EV_Activate, hhVomiter::Event_Trigger ) + EVENT( EV_Erupt, hhVomiter::Event_Erupt ) + EVENT( EV_UnErupt, hhVomiter::Event_UnErupt ) + EVENT( EV_PlayIdle, hhVomiter::Event_PlayIdle ) + EVENT( EV_DamagePulse, hhVomiter::Event_DamagePulse ) + EVENT( EV_MovablePulse, hhVomiter::Event_MovablePulse ) + EVENT( EV_Broadcast_AssignFxErupt, hhVomiter::Event_AssignFxErupt ) +END_CLASS + +hhVomiter::hhVomiter(void) { + // mdl: Moved this here to prevent uninitialized fxErupt from crashing the destructor + fxErupt = NULL; +} + +void hhVomiter::Spawn(void) { + + detectionTrigger = NULL; + + spawnArgs.GetFloat("minDelay", "3", mindelay); + spawnArgs.GetFloat("maxDelay", "7", maxdelay); + secBetweenDamage = spawnArgs.GetFloat("secBetweenDamage"); + secBetweenMovables = spawnArgs.GetFloat("secBetweenMovables"); + minMovableVel = spawnArgs.GetFloat("minMovableVel"); + maxMovableVel = spawnArgs.GetFloat("maxMovableVel"); + removeTime = spawnArgs.GetFloat("movableRemoveTime"); + numMovables = spawnArgs.GetInt("numMovables"); + goAwayChance = spawnArgs.GetFloat("GoAwayChance"); + + idleAnim = GetAnimator()->GetAnim("idle"); + painAnim = GetAnimator()->GetAnim("pain"); + spewAnim = GetAnimator()->GetAnim("spew"); + deathAnim = GetAnimator()->GetAnim("death"); + + bErupting = false; + bIdling = false; + + // setup the clipModel + GetPhysics()->SetContents( CONTENTS_SOLID ); + + // Spawn trigger used for waking it up + SpawnTrigger(); + + //TODO: Spawn this the first time it erupts instead ? + // Spawn FX system + hhFxInfo fxInfo; + fxInfo.SetStart( false ); + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( false ); + fxInfo.Triggered( true ); + BroadcastFxInfo(spawnArgs.GetString("fx_vomit"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFxErupt ); + + StartIdle(); + GoToSleep(); + fl.takedamage = true; +} + +hhVomiter::~hhVomiter() { + SAFE_REMOVE(fxErupt); +} + +void hhVomiter::Save(idSaveGame *savefile) const { + savefile->WriteBool(bErupting); + savefile->WriteBool(bIdling); + savefile->WriteBool(bAwake); + savefile->WriteFloat(mindelay); + savefile->WriteFloat(maxdelay); + + savefile->WriteFloat(secBetweenDamage); + savefile->WriteFloat(secBetweenMovables); + + savefile->WriteInt(idleAnim); + savefile->WriteInt(painAnim); + savefile->WriteInt(spewAnim); + savefile->WriteInt(deathAnim); + savefile->WriteFloat(minMovableVel); + savefile->WriteFloat(maxMovableVel); + savefile->WriteFloat(removeTime); + + savefile->WriteInt(numMovables); + savefile->WriteFloat(goAwayChance); + + savefile->WriteObject(fxErupt); + + detectionTrigger.Save(savefile); +} + +void hhVomiter::Restore( idRestoreGame *savefile ) { + savefile->ReadBool(bErupting); + savefile->ReadBool(bIdling); + savefile->ReadBool(bAwake); + savefile->ReadFloat(mindelay); + savefile->ReadFloat(maxdelay); + + savefile->ReadFloat(secBetweenDamage); + savefile->ReadFloat(secBetweenMovables); + + savefile->ReadInt(idleAnim); + savefile->ReadInt(painAnim); + savefile->ReadInt(spewAnim); + savefile->ReadInt(deathAnim); + savefile->ReadFloat(minMovableVel); + savefile->ReadFloat(maxMovableVel); + savefile->ReadFloat(removeTime); + + savefile->ReadInt(numMovables); + savefile->ReadFloat(goAwayChance); + + savefile->ReadObject( reinterpret_cast(fxErupt) ); + + detectionTrigger.Restore(savefile); +} + +void hhVomiter::SpawnTrigger() { + idDict Args; + + hhUtils::PassArgs( spawnArgs, Args, "triggerpass_" ); + + Args.Set( "target", name.c_str() ); + Args.Set( "bind", name.c_str() ); + + Args.SetVector( "origin", GetOrigin() + spawnArgs.GetVector("offset_trigger") ); + Args.SetMatrix( "rotation", GetAxis() ); + detectionTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &Args ); +} + +void hhVomiter::DormantBegin( void ) { +} + +void hhVomiter::DormantEnd( void ) { +} + +void hhVomiter::Erupt(int repeat) { + if (bErupting || health <= 0 || fl.isDormant) { + if (fl.isDormant) { + UnErupt(); // go to sleep, etc., so we can wake up again later + } + return; + } + + bErupting = true; + StartSound( "snd_erupt", SND_CHANNEL_ANY ); + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, spewAnim, gameLocal.time, 0); + + int startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + + PostEventMS( &EV_MovablePulse, 0 ); // Self repeating moveable spawn + PostEventMS( &EV_DamagePulse, 0 ); // Self repeating damage pulse + + // Activate FX + if (fxErupt) { + fxErupt->PostEventMS( &EV_Activate, 0, this ); + } + + // Stop erupting in a while + PostEventSec( &EV_UnErupt, spawnArgs.GetFloat("vomitTime") ); +} + +void hhVomiter::UnErupt() { + CancelEvents(&EV_Erupt); // Stop any pending events + CancelEvents(&EV_DamagePulse); + CancelEvents(&EV_MovablePulse); + + if (bErupting) { + bErupting = false; + // Deactivate FX + if (fxErupt) { + fxErupt->PostEventMS( &EV_Fx_KillFx, 0 ); + } + } + + GoToSleep(); +} + +void hhVomiter::GoToSleep() { + bAwake = false; +} + +void hhVomiter::WakeUp() { + if (!bAwake && !bErupting && health > 0) { + bAwake = true; + StartSound( "snd_wakeup", SND_CHANNEL_ANY ); + PostEventSec( &EV_Erupt, 0.1+mindelay + gameLocal.random.RandomFloat()*(maxdelay-mindelay), true); + } +} + +bool hhVomiter::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if (painAnim) { + StartSound( "snd_pain", SND_CHANNEL_ANY ); + GetAnimator()->ClearAllAnims( gameLocal.time, 200 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 200); + + int startTime = GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->Length(); + PostEventMS( &EV_PlayIdle, startTime ); + } + return( idEntity::Pain(inflictor, attacker, damage, dir, location) ); +} + +void hhVomiter::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + if (health + damage > 0) { // Only do this the first death + // Clear Event Queue of all eruptions + CancelEvents(&EV_Erupt); + CancelEvents(&EV_DamagePulse); + if (bErupting) { + UnErupt(); + } + + StartSound( "snd_die", SND_CHANNEL_ANY ); + + // Spawn dead effect -- currently an explosion + hhFxInfo fxInfo; + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_death", GetOrigin(), -GetAxis(), &fxInfo ); + + ActivateTargets( attacker ); + + StopIdle(); + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, deathAnim, gameLocal.time, 0); + } + else { // Post-death pain +// GetAnimator()->ClearAllAnims( gameLocal.time, 200 ); +// GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 200); + } +} + +void hhVomiter::StartIdle() { + hhFxInfo fxInfo; + + if (idleAnim && !bIdling && health > 0) { + PostEventMS(&EV_PlayIdle, 0); + StartSound( "snd_idle", SND_CHANNEL_IDLE ); + bIdling = true; + } +} + +void hhVomiter::StopIdle() { + CancelEvents(&EV_PlayIdle); + StopSound( SND_CHANNEL_IDLE ); + bIdling = false; +} + +// +// Events +// + +void hhVomiter::Event_AssignFxErupt( hhEntityFx* fx ) { + fxErupt = fx; +} + +void hhVomiter::Event_PlayIdle() { + GetAnimator()->CycleAnim(ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 100); +} + +void hhVomiter::Event_Trigger( idEntity *activator ) { + WakeUp(); +} + +void hhVomiter::Event_Erupt(int repeat) { + Erupt(repeat); +} + +void hhVomiter::Event_UnErupt() { + UnErupt(); +} + +void hhVomiter::Event_DamagePulse() { + idEntity *touch[ MAX_GENTITIES ]; + int num; + // Damage all entities touching trigger's clipmodel + num = hhUtils::EntitiesTouchingClipmodel( detectionTrigger->GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, MASK_SHOT_BOUNDINGBOX ); + for (int i = 0; i < num; i++ ) { + if( !touch[i] || touch[i] == this ) { + continue; + } + touch[i]->Damage(this, this, GetAxis()[2], spawnArgs.GetString("def_damage"), 1.0f, 0); + } + PostEventSec( &EV_DamagePulse, secBetweenDamage ); +} + +void hhVomiter::Event_MovablePulse() { + // Spawn movables + idDict args; + idEntity *ent; + idVec3 vel, dir; + + const char *movableName = spawnArgs.RandomPrefix("def_movable", gameLocal.random); + if (movableName && *movableName) { + args.Clear(); + args.SetVector("origin", GetOrigin()); + + if (gameLocal.random.RandomFloat() < goAwayChance) { + args.SetBool("removeOnCollision", true); + } + else { + args.SetFloat("removeTime", removeTime); + } + + for (int ix=0; ixGetPhysics()->SetLinearVelocity( vel ); + ent->fl.takedamage = false; // don't let the radius damage affect them + } + } + PostEventSec( &EV_MovablePulse, secBetweenMovables ); // Self repeating event +} diff --git a/neo/prey/Prey/game_vomiter.h b/neo/prey/Prey/game_vomiter.h new file mode 100644 index 00000000..9868a04d --- /dev/null +++ b/neo/prey/Prey/game_vomiter.h @@ -0,0 +1,61 @@ + +#ifndef __GAME_VOMITER_H__ +#define __GAME_VOMITER_H__ + +class hhVomiter : public hhAnimatedEntity { +public: + CLASS_PROTOTYPE( hhVomiter ); + + hhVomiter(); + virtual ~hhVomiter(); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Erupt(int repeat); + void UnErupt(void); + 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 ); + virtual void DormantBegin( void ); + virtual void DormantEnd( void ); + +protected: + void StartIdle(); + void StopIdle(); + void SpawnTrigger(); + void WakeUp(); + void GoToSleep(); + +protected: + void Event_DamagePulse(void); + void Event_Trigger( idEntity *activator ); + void Event_Erupt(int repeat); + void Event_UnErupt(void); + void Event_PlayIdle( void ); + void Event_MovablePulse(); + void Event_AssignFxErupt( hhEntityFx* fx ); + +private: + bool bErupting; + bool bIdling; + bool bAwake; + float mindelay; // Delays until next vomit + float maxdelay; + float secBetweenDamage; + float secBetweenMovables; + int idleAnim; + int painAnim; + int spewAnim; + int deathAnim; + float minMovableVel; + float maxMovableVel; + float removeTime; + int numMovables; + float goAwayChance; + idEntityFx *fxErupt; + + idEntityPtr detectionTrigger; +}; + +#endif /* __GAME_VOMITER_H__ */ diff --git a/neo/prey/Prey/game_weaponHandState.cpp b/neo/prey/Prey/game_weaponHandState.cpp new file mode 100644 index 00000000..27b9097e --- /dev/null +++ b/neo/prey/Prey/game_weaponHandState.cpp @@ -0,0 +1,282 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* +TERMS: + Transition: How to transition up and down + 0 - Abruptly. No up/down anims. Just disappears + 1 - With Anims. Play the up/down anims. +*/ + +CLASS_DECLARATION( idClass, hhWeaponHandState ) +END_CLASS + +/* +============ +hhWeaponHandState::hhWeaponHandState +============ +*/ +hhWeaponHandState::hhWeaponHandState( hhPlayer *aPlayer, + bool aTrackWeapon, int weaponTransition, + bool aTrackHand, int handTransition ){ + + player = aPlayer; + + oldHand = NULL; + oldWeaponNum = 0; + + oldHandUp = handTransition; + oldWeaponUp = weaponTransition; + + trackHand = aTrackHand; + trackWeapon = aTrackWeapon; +} + +void hhWeaponHandState::Save(idSaveGame *savefile) const { + player.Save(savefile); + oldHand.Save(savefile); + savefile->WriteInt(oldWeaponNum); + savefile->WriteInt(oldHandUp); + savefile->WriteInt(oldWeaponUp); + savefile->WriteBool(trackHand); + savefile->WriteBool(trackWeapon); +} + +void hhWeaponHandState::Restore( idRestoreGame *savefile ) { + player.Restore(savefile); + oldHand.Restore(savefile); + savefile->ReadInt(oldWeaponNum); + savefile->ReadInt(oldHandUp); + savefile->ReadInt(oldWeaponUp); + savefile->ReadBool(trackHand); + savefile->ReadBool(trackWeapon); +} + +void hhWeaponHandState::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(player.GetSpawnId(), 32); + msg.WriteBits(oldHand.GetSpawnId(), 32); + + msg.WriteBits(oldWeaponNum, 32); + msg.WriteBits(oldHandUp, 32); + msg.WriteBits(oldWeaponUp, 32); + msg.WriteBits(trackHand, 1); + msg.WriteBits(trackWeapon, 1); +} + +void hhWeaponHandState::ReadFromSnapshot( const idBitMsgDelta &msg ) { + player.SetSpawnId(msg.ReadBits(32)); + oldHand.SetSpawnId(msg.ReadBits(32)); + + oldWeaponNum = msg.ReadBits(32); + oldHandUp = msg.ReadBits(32); + oldWeaponUp = msg.ReadBits(32); + trackHand = !!msg.ReadBits(1); + trackWeapon = !!msg.ReadBits(1); +} + + +/* +============ +hhWeaponHandState::SetPlayer +============ +*/ +void hhWeaponHandState::SetPlayer( hhPlayer *aPlayer ) { + player = aPlayer; +} + +/* +============ +hhWeaponHandState::Save +============ +*/ +int hhWeaponHandState::Archive( const char *newWeaponDef, int weaponTransition, + const char *newHandDef, int handTransition ) { + + if ( !player.IsValid() ) { + gameLocal.Warning( "Error in hhWeaponHandState::Archive, no player set" ); + } + + // Do we care about the old hand? + if ( trackHand ) { + // Save the hand, break if no hand. Must save for the restore + oldHand = player->hand; + + if ( oldHand.IsValid() ) { + // If we are going to raise it later put down + if ( oldHandUp ) { + oldHand->PutAway(); + } + + // Make it disapear + oldHand->Hide(); + + // Make sure it doesn't come back (Ready makes it visible again) + oldHand->CancelEvents(&EV_Hand_Ready); + } + + // Clear the hand out + player->hand = NULL; + } + + // Do we care about the old weapon? + if ( trackWeapon ) { + // Save the weapon, break if none. Must save for the restore + oldWeaponNum = player->GetCurrentWeapon(); + if (oldWeaponNum == -1) + { + oldWeaponNum = player->GetIdealWeapon(); + assert(oldWeaponNum != -1); + } + //Calling destructor directly otherwise it doesn't get called until next frame + if( player->weapon.IsValid() ) { + player->weapon->DeconstructScriptObject(); + player->weapon->Hide(); + } + SAFE_REMOVE( player->weapon ); + } + + // See if we have a new weapon to put up + if ( trackWeapon && newWeaponDef && newWeaponDef[0] ) { + player->ForceWeapon( player->GetWeaponNum(newWeaponDef) ); + + // Weapon_Combat() normally takes care of the lowering/raising logic, so force the raise here + player->weapon->Raise(); + player->SetState( "RaiseWeapon" ); + + // Now that we have the weapon, how do we get it there? + if ( weaponTransition ) { + player->weapon->Raise(); + } + else { + player->weapon->ProcessEvent( &EV_Weapon_State, "Idle", 0 ); + } + } + + // Should we put up a new hand? + if ( trackHand && newHandDef && !player->IsDeathWalking() ) { + hhHand *newHand; //! Debug,remove later + + // Create the new hand + newHand = hhHand::AddHand( player.GetEntity(), newHandDef, true ); + + if ( !newHand ) { + gameLocal.Warning( "Error creating new hand for state change" ); + return( 0 ); + } + + // Make sure the weapon is pop'd into place + newHand->HandleWeapon( player.GetEntity(), newHand, 0, true ); + // Now that the hand is up, how do we get it there? + if ( handTransition ) { + player->hand->Raise(); + } + else { + player->hand->Ready(); + } + } + + return( 0 ); +} + + +/* +============ +hhWeaponHandState::RestoreFromArchive +============ +*/ +int hhWeaponHandState::RestoreFromArchive() { + bool setWeapon = false; + + if ( !player.IsValid() ) { + gameLocal.Warning( "Error in hhWeaponHandState::RestoreFromArchive, no player set" ); + return 0; + } + + if ( trackWeapon ) { + //Calling destructor directly otherwise it doesn't get called until next frame + if( player->weapon.IsValid() ) { + player->weapon->DeconstructScriptObject(); + player->weapon->Hide(); + } + SAFE_REMOVE( player->weapon ); + + // Put up the old weapon if there + if ( oldWeaponNum ) { + player->ForceWeapon( oldWeaponNum ); + + // Weapon_Combat() normally takes care of the lowering/raising logic, so force the raise here + player->SetState( "RaiseWeapon" ); + + if ( oldWeaponUp && player->health <= 0 ) { //HUMANHEAD bjk PCF (4-29-06) - fix weapon after shuttle death + // Make sure it is lowered + player->weapon->ProcessEvent( &EV_Weapon_State, "Raise", 0 ); + player->weapon->UpdateScript(); + + player->weapon->Raise(); + } + else { + player->weapon->ProcessEvent( &EV_Weapon_State, "Idle", 0 ); + } + } else { + player->ForceWeapon( -1 ); + } + setWeapon = true; + } + + + if ( trackHand ) { + // Remove the old hand - Need to set owner to NULL so it won't complain + if ( player->hand.IsValid() ) { + player->hand->ForceRemove(); + } + SAFE_REMOVE( player->hand ); + + hhHand *debugHand = oldHand.GetEntity(); + + // Find the first hand that should be there. Note: The 'if' could be made into a 'while' to do multiple levels + if ( oldHand.IsValid() && !oldHand->IsValidFor( player.GetEntity() ) ) { + idEntityPtr delHand; + + //gameLocal.Printf( "%d Removing weapon state hand\n", gameLocal.time ); + + delHand = oldHand; // Save the old hand for deletion + + // Move to the next hand + oldHand = oldHand->GetPreviousHand(); + debugHand = oldHand.GetEntity(); + + // delete the old old hand + delHand->ForceRemove(); + SAFE_REMOVE( delHand ); + } + + player->hand = oldHand; + + // If valid, put it up/have it handle the weapon + if ( oldHand.IsValid() ) { + player->hand->Show(); + + // Make sure the weapon is in the proper state + player->hand->HandleWeapon( player.GetEntity(), + player->hand.GetEntity(), 0, true ); + + // If they want it raised, raise it + if ( oldHandUp ) { + player->hand->Raise(); + } + // Else just pop it in there! + else { + player->hand->Ready(); + } + } + // If no hands in line, then set the weapon straight if we haven't set it already + else if ( !setWeapon ) { + player->weapon->ProcessEvent( &EV_Weapon_State, "Idle", 0 ); + } //. Got a real hand? + } //. We care about the hand + + return( 0 ); +} diff --git a/neo/prey/Prey/game_weaponHandState.h b/neo/prey/Prey/game_weaponHandState.h new file mode 100644 index 00000000..84d9475b --- /dev/null +++ b/neo/prey/Prey/game_weaponHandState.h @@ -0,0 +1,41 @@ + +#ifndef __PREY_GAME_WEAPONHANDSTATE_H__ +#define __PREY_GAME_WEAPONHANDSTATE_H__ + +class hhWeaponHandState : public idClass { + CLASS_PROTOTYPE( hhWeaponHandState ); + + public: + hhWeaponHandState( hhPlayer *player = NULL, + bool trackWeapon = true, int weaponTransition = 0, + bool trackHand = true, int handTransistion = 0 ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + int Archive( const char *newWeaponDef = NULL, int newWeaponTransition = 0, + const char *newHandDef = NULL, int newHandTransition = 0 ); + + int RestoreFromArchive(); + + void SetPlayer( hhPlayer *player ); + + void SetWeaponTransition( int trans ) { oldWeaponUp = trans; } + + protected: + + idEntityPtr player; + idEntityPtr oldHand; // Previous Hand. NULL if don't care + int oldWeaponNum; + int oldHandUp; + int oldWeaponUp; + + bool trackHand; + bool trackWeapon; +}; + +#endif /* __PREY_GAME_WEAPONHANDSTATE_H__ */ diff --git a/neo/prey/Prey/game_woundmanager.cpp b/neo/prey/Prey/game_woundmanager.cpp new file mode 100644 index 00000000..316a05a1 --- /dev/null +++ b/neo/prey/Prey/game_woundmanager.cpp @@ -0,0 +1,314 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// mdl: Only steam this often to keep performance hits when hitting several pipes +#define STEAM_DELAY 1000.0f + + +hhWoundManagerRenderEntity::hhWoundManagerRenderEntity( const idEntity* self ) { + this->self = self; +} + +hhWoundManagerRenderEntity::~hhWoundManagerRenderEntity() { +} + +void hhWoundManagerRenderEntity::DetermineWoundInfo( const trace_t& trace, const idVec3 velocity, jointHandle_t& jointNum, idVec3& origin, idVec3& normal, idVec3& dir ) { + jointNum = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + + origin = trace.c.point; + normal = trace.c.normal; + dir = velocity; + dir.Normalize(); +} + +void hhWoundManagerRenderEntity::AddWounds( const idDeclEntityDef *def, surfTypes_t matterType, jointHandle_t jointNum, const idVec3& origin, const idVec3& normal, const idVec3& dir ) { + PROFILE_SCOPE("AddWounds", PROFMASK_COMBAT); + if( !def ) { + return; + } + + // Do a check so we don't spawn too many steam effects at once + if( matterType == SURFTYPE_PIPE ) { + if ( gameLocal.time > gameLocal.GetSteamTime() ) { + // Mark the current steam time + gameLocal.SetSteamTime( gameLocal.time + STEAM_DELAY ); + } else { + // Don't steam on this hit + matterType = SURFTYPE_METAL; + } + } + + ApplyWound( def->dict, gameLocal.MatterTypeToMatterKey("fx_wound", matterType), jointNum, origin, normal, dir ); //smoke_wound conflicted with smoke + + const char* impactMarkKey = gameLocal.MatterTypeToMatterKey("mtr", matterType); + ApplyMark( def->dict, impactMarkKey, origin, normal, dir ); + ApplySplatExit( def->dict, impactMarkKey, origin, normal, dir ); +} + +void hhWoundManagerRenderEntity::ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + PROFILE_SCOPE("ApplyMark", PROFMASK_COMBAT); + idList strList; + + const idDict* decalDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceMark_decal"), false ); + if( !decalDict || !self->spawnArgs.GetBool("accepts_decals", "1") ) { + return; + } + + if( !impactMarkKey || !impactMarkKey[0] ) { + return; + } + + idStr impactMark = decalDict->RandomPrefix( impactMarkKey, gameLocal.random ); + if( !impactMark.Length() ) { + return; + } + + if( self->IsType(idBrittleFracture::Type) ) { + static_cast( self.GetEntity() )->ProjectDecal( origin, normal, gameLocal.GetTime(), NULL ); + } else { + hhUtils::SplitString( impactMark, strList ); + + //bjk: uses normal now + float depth = 10.0f; + + for( int ix = strList.Num() - 1; ix >= 0; --ix ) { + gameLocal.ProjectDecal( origin, -normal, depth, true, hhMath::Lerp(decalDict->GetVec2("mark_size"), gameLocal.random.RandomFloat()), strList[ix] ); //HUMANHEAD bjk: using normal + } + } + + //rww - if we were running this code on the server, we could do this: + /* + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + idVec2 markSize; + + msg.Init(msgBuf, sizeof(msgBuf)); + msg.BeginWriting(); + msg.WriteFloat(origin[0]); + msg.WriteFloat(origin[1]); + msg.WriteFloat(origin[2]); + msg.WriteDir(normal, 24); + msg.WriteDir(dir, 24); + + //AGH. rwwFIXME: configstring type system? + msg.WriteString(impactMark); + + if (self->IsType(idBrittleFracture::Type)) + { + msg.WriteBool(true); + msg.WriteShort(self.GetEntity()->entityNumber); + } + else + { + msg.WriteBool(false); + markSize = damageDict->GetVec2("mark_size"); + msg.WriteFloat(markSize[0]); + msg.WriteFloat(markSize[1]); + } + + self.GetEntity()->BroadcastEventReroute(idEntity::EVENT_PROJECT_DECAL, &msg); + */ +} + +void hhWoundManagerRenderEntity::ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + PROFILE_SCOPE("ApplyWound", PROFMASK_COMBAT); + + if ( !g_bloodEffects.GetBool() ) { + return; + } + + if (! (self->fl.acceptsWounds && woundKey && *woundKey)) { + return; + } + + const idDict* woundDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceWound"), false ); + if( !woundDict ) { + return; + } + + const char* woundName = woundDict->RandomPrefix( woundKey, gameLocal.random ); + if( !woundName || !woundName[0] ) { + return; + } + + // for steam sound + idStr sndKey = "snd_"; + sndKey += woundKey; + const char *sndName = woundDict->GetString(sndKey.c_str()); + bool bHasSound = idStr::Length(sndName) > 0; + + /* // Optimization: works but it could cause problems in some of our non-euclidean spaces if + // a portal destination is less than 1024 units away + if (!bHasSound && !gameLocal.isMultiplayer && gameLocal.GetLocalPlayer()) { + // Don't spawn purely visual wounds when behind player + idVec3 playerOrigin = gameLocal.GetLocalPlayer()->GetOrigin(); + idMat3 playerView = gameLocal.GetLocalPlayer()->GetAxis(); + idVec3 toSpot = origin - playerOrigin; + float dist = toSpot.Normalize(); + + // Only do this optimization when within some distance to avoid problems with the effects being on the + // other side of a portal + if (dist < 1024.0f) { + float dot = toSpot * playerView[0]; + + // If spot is behind me and not right on the border, throw it out + if (dot < 0.0f && dist > 64.0f) { + return; + } + } + } + */ + + hhFxInfo fxInfo; + fxInfo.SetNormal( normal ); + if( !self.GetEntity()->IsType(idStaticEntity::Type) ) { + fxInfo.SetEntity( self.GetEntity() ); + } + fxInfo.RemoveWhenDone( true ); + //correct for server: self->BroadcastFxInfo( woundName, origin, dir.ToMat3(), &fxInfo ); + //however this function should -only- be called on the client. + if (gameLocal.isServer && !gameLocal.GetLocalPlayer()) { + gameLocal.Error("hhWoundManagerRenderEntity::ApplyWound called on non-listen server!"); + } + hhEntityFx *fx = self.GetEntity()->SpawnFxLocal( woundName, origin, normal.ToMat3(), &fxInfo, true ); + if (fx) { + fx->fl.neverDormant = true; + + if (bHasSound) { + const idSoundShader *shader = declManager->FindSound( sndName ); + fx->StartSoundShader(shader, SND_CHANNEL_ANY, 0, true); + } + } +} + +void hhWoundManagerRenderEntity::ApplySplatExit( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + PROFILE_SCOPE("ApplySplatExit", PROFMASK_COMBAT); + + if ( !g_bloodEffects.GetBool() ) { + return; + } + + if( gameLocal.random.RandomFloat() > 0.8f ) { + return; + } + + const idDict *splatDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_exitSplat"), false ); + if( !splatDict || !self->spawnArgs.GetBool("produces_splats") ) { + return; + } + + if( !impactMarkKey || !impactMarkKey[0] ) { + return; + } + + const char* decal = splatDict->RandomPrefix( impactMarkKey, gameLocal.random ); + if( !decal || !decal[0] ) { + return; + } + + trace_t trace; + if( gameLocal.clip.TracePoint(trace, origin, origin+dir*200.0f, MASK_SHOT_RENDERMODEL, self.GetEntity() ) ) { + + surfTypes_t matterType = gameLocal.GetMatterType( self.GetEntity(), trace.c.material, "ApplySplatExit" ); + + gameLocal.ProjectDecal( trace.endpos, + -trace.c.normal, + splatDict->GetFloat("splat_dist", "10"), + true, + hhMath::Lerp(splatDict->GetVec2("mark_size"), + gameLocal.random.RandomFloat()), decal ); + } +} + +hhWoundManagerAnimatedEntity::hhWoundManagerAnimatedEntity( const idEntity* self ) : hhWoundManagerRenderEntity(self) { +} + +hhWoundManagerAnimatedEntity::~hhWoundManagerAnimatedEntity() { +} + +void hhWoundManagerAnimatedEntity::ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + idList strList; + + const idDict* overlayDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceMark_overlay"), false ); + if( !overlayDict || !self->spawnArgs.GetBool("accepts_decals", "1") ) { + return; + } + + if( !impactMarkKey && !impactMarkKey[0] ) { + return; + } + + idStr impactMark = overlayDict->RandomPrefix( impactMarkKey, gameLocal.random ); + if( !impactMark.Length() ) { + return; + } + + hhUtils::SplitString( impactMark, strList ); + for( int ix = strList.Num() - 1; ix >= 0; --ix ) { + self->ProjectOverlay( origin, dir, overlayDict->GetFloat("mark_size"), strList[ix] ); + } +} + +void hhWoundManagerAnimatedEntity::ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ) { + + if (! (self->fl.acceptsWounds && woundKey && *woundKey && jointNum != INVALID_JOINT)) { + return; + } + + const idDict* woundDict = gameLocal.FindEntityDefDict( damageDict.GetString("def_entranceWound"), false ); + if( !woundDict ) { + return; + } + + const char* woundName = woundDict->RandomPrefix( woundKey, gameLocal.random ); + if( !woundName || !woundName[0] ) { + return; + } + +#if !GOLD + //correct for server: self->BroadcastFxInfo( woundName, origin, dir.ToMat3(), &fxInfo ); + //however this function should -only- be called on the client. + if (gameLocal.isServer && !gameLocal.GetLocalPlayer()) { + gameLocal.Error("hhWoundManagerRenderEntity::ApplyWound called on non-listen server!"); + } +#endif + +#if 0 + hhFxInfo fxInfo; + fxInfo.SetNormal( normal ); + fxInfo.SetEntity( self.GetEntity() ); + fxInfo.RemoveWhenDone( true ); + + hhEntityFx* fx = self.GetEntity()->SpawnFxLocal( woundName, origin, normal.ToMat3(), &fxInfo, true ); + if (fx) { + fx->fl.neverDormant = true; + fx->BindToJoint( self.GetEntity(), jointNum, true); + } +#else //rww - do this in some way that is less hideous and slow (specialcased here, cause this function gets a lot of use) + //(avoids binding twice and some other needless logic) + if ( !g_skipFX.GetBool() ) { + idDict fxArgs; + + fxArgs.Set( "fx", woundName ); + fxArgs.SetBool( "start", true ); + fxArgs.SetBool( "removeWhenDone", true ); + fxArgs.SetVector( "origin", origin ); + fxArgs.SetMatrix( "rotation", normal.ToMat3() ); + + hhEntityFx *fx = (hhEntityFx *)gameLocal.SpawnClientObject( "func_fx", &fxArgs ); + if (fx) { + hhFxInfo fxInfo; + fxInfo.SetNormal( normal ); + fxInfo.SetEntity( self.GetEntity() ); + fxInfo.RemoveWhenDone( true ); + fx->SetFxInfo( fxInfo ); + + fx->fl.neverDormant = true; + fx->BindToJoint( self.GetEntity(), jointNum, true ); + fx->Show(); + } + } +#endif +} diff --git a/neo/prey/Prey/game_woundmanager.h b/neo/prey/Prey/game_woundmanager.h new file mode 100644 index 00000000..914839f4 --- /dev/null +++ b/neo/prey/Prey/game_woundmanager.h @@ -0,0 +1,34 @@ +#ifndef __HH_WOUND_MANAGER_RENDERENTITY_H +#define __HH_WOUND_MANAGER_RENDERENTITY_H + +class hhWoundManagerRenderEntity { + public: + hhWoundManagerRenderEntity( const idEntity* self ); + virtual ~hhWoundManagerRenderEntity(); + + //Main entry point + void AddWounds( const idDeclEntityDef *def, surfTypes_t matterType, jointHandle_t jointNum, const idVec3& origin, const idVec3& normal, const idVec3& dir ); + virtual void DetermineWoundInfo( const trace_t& trace, const idVec3 velocity, jointHandle_t& jointNum, idVec3& origin, idVec3& normal, idVec3& dir ); + + protected: + virtual void ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + virtual void ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + virtual void ApplySplatExit( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + + protected: + idEntityPtr self; +}; + +class hhWoundManagerAnimatedEntity : public hhWoundManagerRenderEntity { + public: + hhWoundManagerAnimatedEntity( const idEntity* self ); + virtual ~hhWoundManagerAnimatedEntity(); + + protected: + + virtual void ApplyMark( const idDict& damageDict, const char* impactMarkKey, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + virtual void ApplyWound( const idDict& damageDict, const char* woundKey, jointHandle_t jointNum, const idVec3 &origin, const idVec3 &normal, const idVec3 &dir ); + +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/game_wraith.cpp b/neo/prey/Prey/game_wraith.cpp new file mode 100644 index 00000000..c504cc39 --- /dev/null +++ b/neo/prey/Prey/game_wraith.cpp @@ -0,0 +1,974 @@ +//***************************************************************************** +//** +//** GAME_WRAITH.CPP +//** +//** Game code for the Wraith creature +//** +//***************************************************************************** + +// HEADER FILES --------------------------------------------------------------- + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS --------------------------------------------------------------------- + +#define TWEEN_BANK 1000 + +// TYPES ---------------------------------------------------------------------- + +// CLASS DECLARATIONS --------------------------------------------------------- + +const idEventDef EV_Flee( "", NULL ); +const idEventDef EV_PlayAnimMoveEnd( "", NULL ); +extern const idEventDef EV_SpawnDeathWraith; +extern const idEventDef AI_FindEnemy; + +CLASS_DECLARATION( hhMonsterAI, hhWraith ) + EVENT( EV_Flee, hhWraith::Event_Flee ) + EVENT( EV_PlayAnimMoveEnd, hhWraith::Event_PlayAnimMoveEnd ) + EVENT( AI_FindEnemy, hhWraith::Event_FindEnemy ) + EVENT( EV_Activate, hhWraith::Event_Activate ) +END_CLASS + +//============================================================================= +// +// hhWraith::Spawn +// +//============================================================================= +void hhWraith::Spawn(void) { + // List animations + flyAnim = GetAnimator()->GetAnim( "fly" ); + possessAnim = GetAnimator()->GetAnim( "alert" ); + leftAnim = GetAnimator()->GetAnim( "bankLeft" ); + rightAnim = GetAnimator()->GetAnim( "bankRight" ); + fleeAnim = GetAnimator()->GetAnim( "flee" ); + fleeInAnim = GetAnimator()->GetAnim( "fleeIn" ); + + lastAnim = NULL; + + canPossess = spawnArgs.GetBool( "possess", "1" ); + + // Physics and collision information + fl.takedamage = true; + + minDamageDist = spawnArgs.GetFloat( "minDamageDist", "50" ); + + // Initialize flight logic + straightTicks = 0; + damageTicks = 0; + turnTicks = 0; + + isScaling = false; + + // Initially launch the wraith forward + velocity = GetPhysics()->GetAxis()[0] * 100; + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); + fl.neverDormant = true; + + nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200"); + lastCheckOrigin = GetPhysics()->GetOrigin(); + lastDamageTime = spawnArgs.GetFloat( "damageDelay", "0.5" ); + + if ( spawnArgs.GetBool( "spawnFlyUp", "0" ) ) { + state = WS_SPAWN; + + // Orient the wraith pointing upwards + desiredAxis[0] = idVec3( 0, 0, 1 ); + desiredAxis[1] = idVec3( 1, 0, 0 ); + desiredAxis[2] = idVec3( 0, -1, 0 ); + + // Set the speed of this movement + desiredVelocity = spawnArgs.GetFloat( "initialVelocity", "10" ); // TODO: Make this random + + // Calculate how far the wraith should fly and how long that should take + // NOTE: If we make the velocity a random range, then we can make this spawn time constant + countDownTimer = spawnArgs.GetFloat( "spawnTime", "0.5" ) * 60; // Half a second to raise up + } else { + state = WS_FLY; + } + + // Initialize .def values + velocity_xy = spawnArgs.GetFloat( "velocity_xy", "7" ) * (60.0f * USERCMD_ONE_OVER_HZ); + velocity_z = spawnArgs.GetFloat( "velocity_z", "1" ) * (60.0f * USERCMD_ONE_OVER_HZ); + velocity_z_fast = spawnArgs.GetFloat( "velocity_z_fast", "5" ) * (60.0f * USERCMD_ONE_OVER_HZ); + dist_z_close = spawnArgs.GetFloat( "dist_z_close", "5" ); + dist_z_far = spawnArgs.GetFloat( "dist_z_far", "100" ); + + turn_threshold = DEG2RAD( spawnArgs.GetFloat( "turn_threshold", "5" ) ); // CJR PCF 5/17/06: Removed unnecessary 30Hz compensation + turn_radius_max = DEG2RAD( spawnArgs.GetFloat( "turn_radius_max", "130" ) ); // CJR PCF 5/17/06: Remove unnecessary 30Hz compensation + + straight_ticks = spawnArgs.GetInt( "straight_ticks", "30" ) * (USERCMD_HZ / 60.0f); + damage_ticks = spawnArgs.GetFloat( "damage_ticks", "8" ) * (USERCMD_HZ / 60.0f); + turn_ticks = spawnArgs.GetFloat( "turn_ticks", "200" ) * (USERCMD_HZ / 60.0f); + + flee_speed_z = spawnArgs.GetFloat( "flee_speed_z", "10" ) * (60.0f * USERCMD_ONE_OVER_HZ); + + target_z_threshold = spawnArgs.GetFloat( "target_z_threshold", "20" ); + + // JRM: are we waiting for a trigger? Then no sound. + if(!spawnArgs.GetBool("trigger","0")) { + StartSound( "snd_flyloop", SND_CHANNEL_BODY ); + StartSound( "snd_sight", SND_CHANNEL_VOICE ); + + // If this wraith should flee when spawned, then kill it immediately and get it fleeing + // This is used by the possessed kids to spawn a wraith that flees immediately + if ( spawnArgs.GetBool("flee_at_spawn", "0" ) ) { + // Fleeing + Killed( this, this, 0, vec3_origin, 0 ); + + isScaling = true; + scaleStart = spawnArgs.GetFloat( "scaleStart", "1" ); + scaleEnd = spawnArgs.GetFloat( "scaleEnd", "1" ); + scaleTime = spawnArgs.GetFloat( "scaleTime", "1" ); + + // Set data for dynamically scaling the wraith + SetShaderParm( SHADERPARM_ANY_DEFORM, DEFORMTYPE_SCALE ); // Scale deform + SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, scaleStart ); // Scale deform + lastScaleTime = MS2SEC( gameLocal.time ) + scaleTime; + } + } + + Event_SetMoveType(MOVETYPE_FLY); // JRM to get the wraiths to move again + + GetPhysics()->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); + GetPhysics()->SetClipMask( 0 ); + + bFaceEnemy = false; + nextDrop = 0; + nextChatter = 0; + nextPossessTime = 0; + + minChatter = SEC2MS(spawnArgs.GetFloat( "min_chatter_time", "3" )); + maxChatter = SEC2MS(spawnArgs.GetFloat( "max_chatter_time", "6" )); + + if ( !IsHidden() ) { + Hide(); + PostEventMS( &EV_Activate, 0, this ); + } +} + +//============================================================================= +// +// hhWraith::~hhWraith +// +//============================================================================= +hhWraith::~hhWraith() { + StopSound( SND_CHANNEL_BODY ); +} + +//============================================================================= +// +// hhWraith::Save +// +//============================================================================= +void hhWraith::Save( idSaveGame *savefile ) const { + savefile->WriteInt( lastAnim ); + savefile->WriteBool( canPossess ); + savefile->WriteVec3( velocity ); + savefile->WriteInt( damageTicks ); + savefile->WriteInt( straightTicks ); + savefile->WriteInt( turnTicks ); + savefile->WriteVec3( lastCheckOrigin ); + savefile->WriteFloat( lastDamageTime ); + savefile->WriteInt( state ); + savefile->WriteFloat( velocity_xy ); + savefile->WriteFloat( velocity_z ); + savefile->WriteFloat( velocity_z_fast ); + savefile->WriteFloat( dist_z_close ); + savefile->WriteFloat( dist_z_far ); + savefile->WriteFloat( turn_threshold ); + savefile->WriteFloat( turn_radius_max ); + savefile->WriteInt( straight_ticks ); + savefile->WriteInt( damage_ticks ); + savefile->WriteInt( turn_ticks ); + savefile->WriteFloat( flee_speed_z ); + savefile->WriteFloat( target_z_threshold ); + savefile->WriteFloat( minDamageDist ); + savefile->WriteBool( isScaling ); + savefile->WriteFloat( scaleStart ); + savefile->WriteFloat( scaleEnd ); + savefile->WriteFloat( lastScaleTime ); + savefile->WriteInt( countDownTimer ); + savefile->WriteMat3( desiredAxis ); + savefile->WriteFloat( desiredVelocity ); + savefile->WriteBool( bFaceEnemy ); + fxFly.Save( savefile ); +} + +//============================================================================= +// +// hhWraith::Restore +// +//============================================================================= +void hhWraith::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( lastAnim ); + savefile->ReadBool( canPossess ); + savefile->ReadVec3( velocity ); + savefile->ReadInt( damageTicks ); + savefile->ReadInt( straightTicks ); + savefile->ReadInt( turnTicks ); + savefile->ReadVec3( lastCheckOrigin ); + savefile->ReadFloat( lastDamageTime ); + savefile->ReadInt( (int &)state ); + savefile->ReadFloat( velocity_xy ); + savefile->ReadFloat( velocity_z ); + savefile->ReadFloat( velocity_z_fast ); + savefile->ReadFloat( dist_z_close ); + savefile->ReadFloat( dist_z_far ); + savefile->ReadFloat( turn_threshold ); + savefile->ReadFloat( turn_radius_max ); + savefile->ReadInt( straight_ticks ); + savefile->ReadInt( damage_ticks ); + savefile->ReadInt( turn_ticks ); + savefile->ReadFloat( flee_speed_z ); + savefile->ReadFloat( target_z_threshold ); + savefile->ReadFloat( minDamageDist ); + savefile->ReadBool( isScaling ); + savefile->ReadFloat( scaleStart ); + savefile->ReadFloat( scaleEnd ); + savefile->ReadFloat( lastScaleTime ); + savefile->ReadInt( countDownTimer ); + savefile->ReadMat3( desiredAxis ); + savefile->ReadFloat( desiredVelocity ); + savefile->ReadBool( bFaceEnemy ); + + // List animations + flyAnim = GetAnimator()->GetAnim( "fly" ); + possessAnim = GetAnimator()->GetAnim( "alert" ); + leftAnim = GetAnimator()->GetAnim( "bankLeft" ); + rightAnim = GetAnimator()->GetAnim( "bankRight" ); + fleeAnim = GetAnimator()->GetAnim( "flee" ); + fleeInAnim = GetAnimator()->GetAnim( "fleeIn" ); + + canPossess = spawnArgs.GetBool( "possess", "1" ); + + minDamageDist = spawnArgs.GetFloat( "minDamageDist", "50" ); + + // Initialize .def values + velocity_xy = spawnArgs.GetFloat( "velocity_xy", "7" ); + velocity_z = spawnArgs.GetFloat( "velocity_z", "1" ); + velocity_z_fast = spawnArgs.GetFloat( "velocity_z_fast", "5" ); + dist_z_close = spawnArgs.GetFloat( "dist_z_close", "5" ); + dist_z_far = spawnArgs.GetFloat( "dist_z_far", "100" ); + + turn_threshold = DEG2RAD( spawnArgs.GetFloat( "turn_threshold", "5" ) ); + turn_radius_max = DEG2RAD( spawnArgs.GetFloat( "turn_radius_max", "130" ) ); + + straight_ticks = spawnArgs.GetInt( "straight_ticks", "30" ) * (60.0f * USERCMD_ONE_OVER_HZ); + damage_ticks = spawnArgs.GetFloat( "damage_ticks", "8" ) * (60.0f * USERCMD_ONE_OVER_HZ); + turn_ticks = spawnArgs.GetFloat( "turn_ticks", "200" ) * (60.0f * USERCMD_ONE_OVER_HZ); + + flee_speed_z = spawnArgs.GetFloat( "flee_speed_z", "10" ); + + target_z_threshold = spawnArgs.GetFloat( "target_z_threshold", "20" ); + + nextDrop = 0; + nextChatter = 0; + nextPossessTime = gameLocal.time + 500; // Don't possess for half a second after loading + minChatter = SEC2MS(spawnArgs.GetFloat( "min_chatter_time", "3" )); + maxChatter = SEC2MS(spawnArgs.GetFloat( "max_chatter_time", "6" )); + + fxFly.Restore( savefile ); + + nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200"); + scaleTime = spawnArgs.GetFloat( "scaleTime", "1" ); +} + +//============================================================================= +// +// hhWraith::Think +// +//============================================================================= +void hhWraith::Think(void) { + if(!IsHidden()) { // JRM - NO TRACES WHEN HIDDEN! + CheckCollisions(); + + if (gameLocal.time > nextDrop) { + hhMonsterAI::Damage(this, this, vec3_origin, "damage_wraithminion", 0.05f, INVALID_JOINT); + nextDrop = gameLocal.time + 1000; + } + + if (gameLocal.time > nextChatter) { + StartSound( "snd_chatter", SND_CHANNEL_VOICE ); + nextChatter = gameLocal.time + (minChatter + gameLocal.random.RandomFloat() * (maxChatter - minChatter)); + } + } + + // Scale the wraith + if ( isScaling ) { + float delta = lastScaleTime - MS2SEC( gameLocal.time ); + if ( delta > 0 ) { + SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, hhMath::Lerp( scaleEnd, scaleStart, delta / scaleTime ) ); + } + } + + hhMonsterAI::Think(); // JRM: Bypassing hhAI::Think() +} + +//============================================================================= +// +// hhWraith::Event_Activate +// +//============================================================================= +void hhWraith::Event_Activate(idEntity *activator) { + if ( IsHidden() ) { + TeleportIn(activator); + return; + } + if ( spawnArgs.GetBool("flee_at_spawn", "0" ) ) { + // Fleeing + Killed( this, this, 0, vec3_origin, 0 ); + + isScaling = true; + scaleStart = spawnArgs.GetFloat( "scaleStart", "1" ); + scaleEnd = spawnArgs.GetFloat( "scaleEnd", "1" ); + scaleTime = spawnArgs.GetFloat( "scaleTime", "1" ); + + // Set data for dynamically scaling the wraith + SetShaderParm( SHADERPARM_ANY_DEFORM, DEFORMTYPE_SCALE ); // Scale deform + SetShaderParm( SHADERPARM_ANY_DEFORM_PARM1, scaleStart ); // Scale deform + lastScaleTime = MS2SEC( gameLocal.time ) + scaleTime; + } + + hhMonsterAI::Event_Activate(activator); + + // we've been waiting for a trigger + if (spawnArgs.GetBool("trigger")) { + StartSound( "snd_flyloop", SND_CHANNEL_BODY ); + StartSound( "snd_sight", SND_CHANNEL_VOICE ); + } + + + const char *defName = spawnArgs.GetString("fx_fly"); + if (defName && defName[0]) { + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient ); + if (fxFly.IsValid()) { + fxFly->fl.neverDormant = true; + + fxFly->fl.networkSync = false; + fxFly->fl.clientEvents = true; + } + } +} + +//============================================================================= +// +// hhWraith::FindEnemy +// +//============================================================================= +void hhWraith::Event_FindEnemy( int useFOV ) { + int i; + idEntity *ent; + idActor *closest; + idActor *actor; + float closestDist; + float distSquared; + + closest = NULL; + closestDist = 99999999.0f; + + if (team != 0) { // Search players + for ( i = 0; i < gameLocal.numClients ; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idActor::Type ) ) { + continue; + } + + if ( ent->IsType( hhPlayer::Type ) ) { // Check if the player is really dead or deathwalking + hhPlayer *player = static_cast< hhPlayer * >( ent ); + if ( player->IsDead() || player->IsDeathWalking() ) { + continue; + } + } + + // Find the closest enemy + distSquared = ( GetOrigin() - ent->GetOrigin() ).LengthSqr(); + if ( distSquared < closestDist ) { + closestDist = distSquared; + closest = static_cast( ent ); + } + } + } + else { // Search monsters + int numMonsters = hhMonsterAI::allSimpleMonsters.Num(); + for ( i = 0; i < numMonsters ; i++ ) { + actor = hhMonsterAI::allSimpleMonsters[ i ]; + + if ( !actor || actor==this ) { + continue; + } + + // Ignore dormant entities! + if (actor->IsHidden() || actor->fl.isDormant) { + continue; + } + + if ( (actor->health <= 0) || !(ReactionTo(actor) & ATTACK_ON_SIGHT) ) { + continue; + } + + distSquared = (actor->GetOrigin() - GetOrigin()).LengthSqr(); + if ( distSquared < closestDist ) { + closestDist = distSquared; + closest = actor; + } + } + } + + if (closest) { + SetEnemy( closest ); + } + else { + // FIXME: No enemies found, should we just go away or circle for a while? + Damage(this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT); + } + + idThread::ReturnEntity( closest ); +} + +//============================================================================= +// +// hhWraith::UpdateEnemyPosition +// +// This is here to override the default UpdateEnemyPosition, which +// runs code that is unnecessary for the wraiths +//============================================================================= +void hhWraith::UpdateEnemyPosition( void ) { + // Do nothing. Wraith's don't need to check if the enemy is no longer visible, since they can go through walls +} + +//============================================================================= +// +// hhWraith::EnemyDead +// +//============================================================================= +void hhWraith::EnemyDead() { + if ( !enemy.IsValid() || !enemy->IsType( hhPlayer::Type ) ) { // Only consider the enemy dead if the enemy is not a player + hhMonsterAI::EnemyDead(); + } else if ( enemy.IsValid() && enemy->IsType( hhPlayer::Type ) ) { // If the player is deathwalking, then the enemy is dead + hhPlayer *player = static_cast< hhPlayer * >( enemy.GetEntity() ); + if ( player->IsDead() || player->IsDeathWalking() ) { + hhMonsterAI::EnemyDead(); + } + } +} + +//============================================================================= +// +// hhWraith::FlyMove +// +//============================================================================= +void hhWraith::FlyMove( void ) { + // The state of the creature determines how it will move + switch ( state ) { + case WS_SPAWN: + FlyUp(); // Flying up right after spawn + break; + case WS_FLY: + FlyToEnemy(); + break; + case WS_FLEE: // Flying away + FlyAway(); + break; +// case WS_POSSESS_CHARGE: +// WraithDamageEnemy(); +// break; + case WS_STILL: // Wraith is not moving at all (playing a specific anim, etc) + return; + } + + // run the physics for this frame + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( disableGravity ); + RunPhysics(); +} + +//============================================================================= +// +// hhWraith::FlyToEnemy +// +//============================================================================= +void hhWraith::FlyToEnemy( void ) { + float delta = 0; + bool dir = false; + int anim; + + if ( !enemy.IsValid() ) { + PostEventMS( &AI_FindEnemy, 1, 0 ); + return; + } + + if (canPossess) { + // Determine which direction the wraith needs to turn to the enemy (if the wraith should turn) + if ( --straightTicks <= 0 ) { + dir = GetFacePosAngle( enemy->GetOrigin(), delta ); + + turnTicks++; // Add in the number of ticks that the wraith has been turning + // If the wraith has been turning for a very long period of time (more than 4 seconds), then force the wraith to go straight + + // Vary turn_ticks by +/- 25 depending on the DDA value + if ( turnTicks > this->turn_ticks - (25 * ((gameLocal.GetDDAValue() - 0.5f) * 2))) { + dir = 0; + delta = 0; + turnTicks = 0; + deltaViewAngles.yaw = 0; + } + } else { + dir = 0; + delta = 0; + deltaViewAngles.yaw = 0; + turnTicks = 0; + } + } + + // Turn the wraith, and set the correct bank animation + anim = flyAnim; + if ( delta > this->turn_threshold ) { // Wraith should turn + if ( dir ) { // Turn to the left + deltaViewAngles.yaw = this->turn_radius_max; + anim = leftAnim; + } else { + deltaViewAngles.yaw = -this->turn_radius_max; + anim = rightAnim; + } + } else if ( straightTicks <= 0 ) { // Straight at the player + deltaViewAngles.yaw = 0; + // Vary straight_ticks by +/- 25 depending on the DDA value + straightTicks = this->straight_ticks - (25 * ((gameLocal.GetDDAValue() - 0.5f) * 2)); // Stay on this path for a short period of time + } + + // Actually set the animation + if ( anim != lastAnim ) { + GetAnimator()->ClearAllAnims( gameLocal.time, TWEEN_BANK ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, TWEEN_BANK ); + lastAnim = anim; + } + + velocity = viewAxis[0] * this->velocity_xy; + if ( damageTicks > 0 ) { // Fly slower if damaged + velocity *= 0.85f; + velocity += 10 * idVec3( gameLocal.random.CRandomFloat(), gameLocal.random.CRandomFloat(), gameLocal.random.CRandomFloat() ); + + damageTicks--; + } + + // Equalize the z-height of the wraith relative to its target + idVec3 enemyOrigin = enemy->GetEyePosition(); + idVec3 origin = GetPhysics()->GetOrigin(); + + // Factor in fly_offset + enemyOrigin.z += fly_offset; + + // Adjust z-velocity based upon the delta Z to target + velocity.z = 0; + if( ( origin.z > enemyOrigin.z + this->target_z_threshold ) || ( origin.z < enemyOrigin.z - this->target_z_threshold ) ) { + float newZ = enemyOrigin.z + target_z_threshold * 0.5f; + float deltaZ = newZ - origin.z; + if ( abs( deltaZ ) > this->dist_z_far ) { + velocity.z = this->velocity_z_fast * ( ( deltaZ > 0 ) ? 1 : -1 ); + } else if ( abs( deltaZ ) > this->dist_z_close ) { + velocity.z = this->velocity_z * ( ( deltaZ > 0 ) ? 1 : -1 ); + } + } + + // 1.5x the velocity if DDA is 1.0, half it for DDA = 0.0, leave it the same for normal (DDA = 0.5) + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + (velocity * (gameLocal.GetDDAValue() + 0.5f))); + + UpdateVisuals(); +} + +//============================================================================= +// +// hhWraith::FlyAway +// +// Wraiths will automatically fly away from their enemy and up +// The Wraith will eventually be removed from the world in the CheckFleeRemove function +//============================================================================= +void hhWraith::FlyAway( void ) { + + velocity.x = 0; + velocity.y = 0; + velocity.z = this->flee_speed_z; + + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + velocity ); + + UpdateVisuals(); + + // Check to see if the wraith has flown out of the world + CheckFleeRemove(); +} + +//============================================================================= +// +// hhWraith::FlyUp +// +// mdl: Wraiths will automatically fly up after being spawned if spawnFlyUp is set to 1 +// NOT related to flying away during death, that's FlyAway() +//============================================================================= +void hhWraith::FlyUp( void ) { + idVec3 velocity; + + // Turn towards desired angle + // TODO: Make this smooth + SetAxis( desiredAxis ); + + // Apply velocity + velocity = desiredAxis[0] * desiredVelocity; + SetOrigin( GetOrigin() + velocity ); + + UpdateVisuals(); + + // If the wraith is near the end of the spawn, then smoothly rotate the wraith to the horizontal + if ( --countDownTimer <= 0 ) { + state = WS_FLY; + } +} + +//============================================================================= +// +// hhWraith::TurnTowardEnemy +// +//============================================================================= +void hhWraith::TurnTowardEnemy() { + float delta; + bool dir; + + if ( !enemy.IsValid() ) { + return; + } + + // Face torward the enemy + dir = GetFacePosAngle( enemy->GetOrigin(), delta ); + + if ( delta > turn_threshold ) { + // Turn based upon delta angles + if ( dir ) { // Turn to the right + deltaViewAngles.yaw = turn_radius_max; + } else { + deltaViewAngles.yaw = -turn_radius_max; + } + } + + UpdateVisuals(); +} + +//============================================================================= +// +// hhWraith::CheckFleeRemove +// +//============================================================================= +void hhWraith::CheckFleeRemove( void ) { + // Check if the Wraith has flown out of the world + if( gameLocal.clip.Contents( GetPhysics()->GetOrigin() + idVec3(0, 0, 48), GetPhysics()->GetClipModel(), viewAxis, CONTENTS_SOLID, this ) ) { + Hide(); + PostEventSec( &EV_Remove, 2.0f ); // Keep the wraith around for a few seconds so the sounds can finish + state = WS_STILL; // No reason to keep moving the wraith + } +} + +//============================================================================= +// +// hhWraith::Damage +// +//============================================================================= +void hhWraith::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + damageTicks = this->damage_ticks; +} + +//============================================================================= +// +// hhWraith::Killed +// +//============================================================================= +void hhWraith::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhFxInfo fx; + //fx.SetEntity( this ); + fx.RemoveWhenDone( true ); + const char *deathFX; + + // Stop flyloop and chatter sounds, if any + StopSound( SND_CHANNEL_BODY ); + StopSound( SND_CHANNEL_VOICE ); + + if ( inflictor == this ) { + // If we timed out, don't drop a soul + spawnArgs.Delete( "def_dropSoul" ); + deathFX = spawnArgs.GetString( "fx_death" ); + } else { + // Use alternate death fx if we were killed by the player + deathFX = spawnArgs.GetString( "fx_death2" ); + } + + SpawnFxLocal( deathFX, GetOrigin(), GetAxis(), &fx ); + + SAFE_REMOVE(fxFly); + + hhMonsterAI::Killed( inflictor, attacker, damage, dir, location ); + + StartSound( "snd_death", SND_CHANNEL_VOICE ); + + Hide(); + + // activate targets + //ActivateTargets( this ); + + PostEventSec( &EV_Remove, 4 ); +} + +//============================================================================= +// +// hhWraith::CheckCollisions +// +// Check if the wraith is entering or exiting the world +//============================================================================= +void hhWraith::CheckCollisions( void ) { + + if (gameLocal.time < nextCheckTime) { + return; + } + + hhFxInfo fxInfo; + trace_t tr1; + trace_t tr2; + idVec3 jointPos; + idMat3 jointAxis; + + if (!GetJointWorldTransform( spawnArgs.GetString("joint_collision"), jointPos, jointAxis )) { + jointPos = GetOrigin(); + jointAxis = GetAxis(); + } + + // Trace to determine if the wraith just flew through a wall, or just emerged from a wall + // For speed, this only checks against the world + idVec3 dir, point; + gameLocal.clip.TracePoint( tr1, jointPos, lastCheckOrigin, MASK_SHOT_BOUNDINGBOX, this ); // Check if we entered something solid + gameLocal.clip.TracePoint( tr2, lastCheckOrigin, jointPos, MASK_SHOT_BOUNDINGBOX, this ); // Check if we exited something solid + + idVec3 playerOrigin = gameLocal.GetLocalPlayer()->GetOrigin(); + if ( tr1.fraction < 1.0f ) { + point = tr1.c.point; + dir = tr1.c.normal; + } + else if ( tr2.fraction < 1.0f) { + point = tr2.c.point; + dir = tr2.c.normal; + } + if ( tr1.fraction < 1.0f && tr2.fraction < 1.0f ) { + if ( (tr1.c.point - playerOrigin).LengthSqr() <= (tr2.c.point - playerOrigin).LengthSqr() ) { + point = tr1.c.point; + dir = tr1.c.normal; + } + else { + point = tr2.c.point; + dir = tr2.c.normal; + } + } + + if ( tr1.fraction < 1.0f || tr2.fraction < 1.0f ) { + // Spawn an FX at wall collision + //TODO: IMPORTANT: This currently spawns a system every check while passing through something. This creates enormous + // amounts of particles. Fix so there is a single system spawned on the way in and a single system spawned on the way out. + //TODO: This currently hits players so you get a big blob of particles right in your face and right behind you. + const char *hitWallFxName = spawnArgs.GetString("fx_hitwall"); + if (hitWallFxName && *hitWallFxName) { + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfo(hitWallFxName, point, mat3_identity, &fxInfo); + } + + + // Project a spooge decal on the wall here + const char *decalName = spawnArgs.GetString("mtr_decal", NULL); + if (decalName && *decalName) { + float depth = spawnArgs.GetFloat("decal_trace"); + float size = spawnArgs.GetFloat( "decal_size" ); + +// gameRenderWorld->DebugArrow(colorRed, point, point + dir*128, 5, 10000); + + gameLocal.ProjectDecal(tr1.c.point, -tr1.c.normal, depth, true, size, decalName); + gameLocal.ProjectDecal(tr2.c.point, -tr2.c.normal, depth, true, size, decalName); + } + } + + // Possession check + if ( canPossess && gameLocal.time > nextPossessTime ) { + gameLocal.clip.TraceBounds( tr1, lastCheckOrigin, jointPos, GetPhysics()->GetBounds(), MASK_SHOT_BOUNDINGBOX, this ); + if ( gameLocal.entities[ tr1.c.entityNum ] && gameLocal.entities[ tr1.c.entityNum ]->IsType( idActor::Type ) ) { + // If we hit a possessable actor, possess them + idActor *actor = reinterpret_cast ( gameLocal.entities[ tr1.c.entityNum ] ); + if ( actor != lastActor.GetEntity() ) { + // Play the attack sound + StartSound( "snd_attack", SND_CHANNEL_VOICE ); + if ( actor->IsType( hhPlayer::Type ) ) { + hhPlayer *player = reinterpret_cast ( gameLocal.entities[ tr1.c.entityNum ] ); + int power = player->GetSpiritPower(); + power -= 25; + + if ( power <= 0 ) { // Possess the player + power = 0; + // mdl: Wraiths no longer possess + //if ( player->CanBePossessed() ) { + // Possessable + //WraithPossess( player ); + //} else + if ( enemy.GetEntity() == player && player->IsSpiritWalking() && !player->IsPossessed() ) { + // Send the player back to his body + reinterpret_cast (enemy.GetEntity())->DisableSpiritWalk(5); + // Don't immediately possess if the players body happens to be right there. + nextPossessTime = gameLocal.time + 1000; + } + } else { // Gain health from the player's spirit power + health += 5; + if ( health > spawnHealth ) { + health = spawnHealth; + } + } + player->SetSpiritPower( power ); + + }// else if ( actor->CanBePossessed() ) { + // // Possess the actor + // WraithPossess( actor ); + //} + } + lastActor = actor; + } else { + lastActor = NULL; + } + } + + nextCheckTime = gameLocal.time + spawnArgs.GetInt("trace_check_time", "200"); + lastCheckOrigin = jointPos; +} + +//============================================================================= +// +// hhWraith::Event_Flee +// +// The wraith is about to flee and has already played the flee intro anim +//============================================================================= +void hhWraith::Event_Flee( ) { + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, fleeAnim, gameLocal.time, 0 ); + state = WS_FLEE; +} + +//============================================================================= +// +// hhWraith::PlayAnimMove +// +//============================================================================= +void hhWraith::PlayAnimMove( int anim, int blendTime ) { + GetAnimator()->ClearAllAnims( gameLocal.time, blendTime ); + GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, blendTime ); + state = WS_STILL; + PostEventMS( &EV_PlayAnimMoveEnd, GetAnimator()->GetAnim( possessAnim )->Length() - blendTime ); +} + +//============================================================================= +// +// hhWraith::PlayAnimMoveEnd +// +//============================================================================= +void hhWraith::PlayAnimMoveEnd() { + idVec3 boneOrigin; + idMat3 boneAxis; + GetJointWorldTransform( "Head", boneOrigin, boneAxis ); + + GetPhysics()->SetOrigin( boneOrigin ); // Move the wraith to the end of the animation + + GetAnimator()->ClearAllAnims( gameLocal.time, 0 ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, flyAnim, gameLocal.time, 0 ); + state = WS_FLY; + + damageTicks = 0; +} + +//============================================================================= +// +// hhWraith::WraithPossess +// +// Actually possess an actor +//============================================================================= +void hhWraith::WraithPossess( idActor *actor ) { + hhFxInfo fxInfo; + + actor->Possess( this ); + + // Spawn in a flash + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_possessionFlash", GetOrigin(), GetAxis(), &fxInfo ); + + // activate targets + ActivateTargets( actor ); + + PostEventMS( &EV_Remove, 0 ); +} + + +//============================================================================= +// +// hhWraith::Event_PlayAnimMoveEnd +// +//============================================================================= +void hhWraith::Event_PlayAnimMoveEnd( ) { + PlayAnimMoveEnd(); +} + +void hhWraith::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) { + // Only switch to the proxy if the player has no spirit power + //if ( player->GetSpiritPower() == 0 ) { + // hhMonsterAI::Event_EnemyIsSpirit( player, proxy ); + //} +} + +void hhWraith::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) { + // We don't care whether we can see the enemy or not + enemy = player; +} + +void hhWraith::TeleportIn( idEntity *activator ) { + if ( spawnArgs.GetBool( "quickSpawn" ) ) { + PostEventMS( &EV_Show, 0 ); + PostEventMS( &EV_Activate, 50, activator ); + } + else { + hhFxInfo fx; + fx.RemoveWhenDone( true ); + fx.SetEntity( this ); + SpawnFxLocal( spawnArgs.GetString( "fx_spawn" ), GetOrigin(), GetAxis(), &fx ); + PostEventMS( &EV_Show, 1000 ); + PostEventMS( &EV_Activate, 1050, activator ); + } +} + +void hhWraith::Portalled(idEntity *portal) { + hhMonsterAI::Portalled( portal ); + if ( fxFly.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + + fxFly->SetFxInfo( fxInfo ); + + // Reset the fx system + fxFly->Stop(); + fxFly->Start( gameLocal.time ); + } +} + diff --git a/neo/prey/Prey/game_wraith.h b/neo/prey/Prey/game_wraith.h new file mode 100644 index 00000000..a983852d --- /dev/null +++ b/neo/prey/Prey/game_wraith.h @@ -0,0 +1,128 @@ + +#ifndef __GAME_WRAITH_H__ +#define __GAME_WRAITH_H__ + +typedef enum wraithState_s { + WS_SPAWN = 0, + WS_FLY, + WS_FLEE, + WS_POSSESS_CHARGE, + WS_DEATH_CHARGE, + WS_STILL +} wraithState_t; + +class hhWraith : public hhMonsterAI { + public: + CLASS_PROTOTYPE(hhWraith); + + void Spawn(void); + virtual ~hhWraith(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void UpdateEnemyPosition( 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 Think( void ); + + virtual int HasAmmo( ammo_t type, int amount ) { return 0; } + virtual bool UseAmmo( ammo_t type, int amount ) { return false ; } + + protected: + virtual void EnemyDead(); + + // State flight functions + virtual void FlyUp( void ); + virtual void FlyMove( void ); + virtual void FlyToEnemy( void ); + virtual void FlyAway( void ); + + void CheckFleeRemove( void ); + void WraithPossess( idActor *actor ); + void CheckCollisions( void ); + + void PlayAnimMove( int anim, int blendTime ); + void PlayAnimMoveEnd(); + void TurnTowardEnemy(); + + void Event_FindEnemy( int useFOV ); + void Event_TurnTowardEnemy(); + void Event_Flee(); + void Event_PlayAnimMoveEnd( ); + virtual void Event_Activate(idEntity *activator); + + virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ); + virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ); + + virtual void TeleportIn( idEntity *activator ); + virtual void StartDisposeCountdown() { } // Doesn't apply to wraiths + + virtual void Portalled(idEntity *portal); + + protected: + // Animations + int flyAnim; + int possessAnim; + int leftAnim; + int rightAnim; + int fleeAnim; + int fleeInAnim; + + int lastAnim; // Used in turning flight logic + + // Variables + bool canPossess; + + idVec3 velocity; + + int damageTicks; // # of ticks to act damaged (fly slower, flash red) + int straightTicks; // # of ticks to fly straight + int turnTicks; // # if ticks when turning before the wraith will be forced to fly straight + + int nextCheckTime; + idVec3 lastCheckOrigin; + float lastDamageTime; // used to delay the damage check after applying damage + + wraithState_t state; + + // .def file variables + float velocity_xy; + float velocity_z; + float velocity_z_fast; + float dist_z_close; + float dist_z_far; + float turn_threshold; + float turn_radius_max; + int straight_ticks; // Number of ticks to delay before turning + int damage_ticks; + int turn_ticks; // Max time to spend turning before going straight + float flee_speed_z; + float target_z_threshold; + + float minDamageDist; // Damage distance check + + // Scale variables + bool isScaling; + float scaleStart; + float scaleEnd; + float scaleTime; + float lastScaleTime; + + int countDownTimer; + idMat3 desiredAxis; + float desiredVelocity; + + bool bFaceEnemy; + int nextDrop; + int nextChatter; + int minChatter; + int maxChatter; + + idEntityPtr lastActor; + + int nextPossessTime; + + idEntityPtr fxFly; +}; + +#endif /* __GAME_WRAITH_H__ */ diff --git a/neo/prey/Prey/game_zone.cpp b/neo/prey/Prey/game_zone.cpp new file mode 100644 index 00000000..eadc6a8c --- /dev/null +++ b/neo/prey/Prey/game_zone.cpp @@ -0,0 +1,1182 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_SetGravityVector("setgravity", "v"); +const idEventDef EV_SetGravityFactor("setgravityfactor", "f"); +const idEventDef EV_DeactivateZone("", NULL); + +//----------------------------------------------------------------------- +// +// hhZone +// +// NOTE: You do not get a EntityLeaving() callback for entities that are +// removed. Could possibly use hhSafeEntitys to tell when they are removed +// but what's the point of calling EntityLeaving() with an invalid pointer. +//----------------------------------------------------------------------- + +ABSTRACT_DECLARATION(hhTrigger, hhZone) + EVENT( EV_DeactivateZone, hhZone::Event_TurnOff ) + EVENT( EV_Enable, hhZone::Event_Enable ) + EVENT( EV_Disable, hhZone::Event_Disable ) + EVENT( EV_Touch, hhZone::Event_Touch ) +END_CLASS + +//NOTE: If this works, this entity can cease inheriting from trigger, just need to take the +// tracemodel creation logic, isSimpleBox variable, make our own enable/disable functions +// touch goes away, triggeraction goes away, much simpler interface +#define ZONES_ALWAYS_ACTIVE 1 // testing: want to be able to use dormancy to turn off --pdm + +void hhZone::Spawn(void) { + slop = 0.0f; // Extra slop for bounds check +#if !ZONES_ALWAYS_ACTIVE + fl.neverDormant = true; +#endif + +#if ZONES_ALWAYS_ACTIVE + fl.neverDormant = false; + BecomeActive(TH_THINK); + bActive = true; + bEnabled = true; +#endif +} + +void hhZone::Save(idSaveGame *savefile) const { + savefile->WriteInt(zoneList.Num()); // idList + for (int i=0; iWriteInt(zoneList[i]); + } + + savefile->WriteFloat(slop); +} + +void hhZone::Restore( idRestoreGame *savefile ) { + int num; + + zoneList.Clear(); // idList + savefile->ReadInt(num); + zoneList.SetNum(num); + for (int i=0; iReadInt(zoneList[i]); + } + + savefile->ReadFloat(slop); +} + +bool hhZone::ValidEntity(idEntity *ent) { + return (ent && ent!=this && + ent->GetPhysics() && + !ent->GetPhysics()->IsType(idPhysics_Static::Type) && + ent->GetPhysics()->GetContents() != 0); +} + +void hhZone::Empty() { +} + +bool hhZone::ContainsEntityOfType(const idTypeInfo &t) { + idEntity *touch[ MAX_GENTITIES ]; + idBounds clipBounds; + + clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() ); + int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX, touch, MAX_GENTITIES ); + for (int i=0; iIsType(t)) { + gameLocal.Printf("Contains a %s\n", t.classname); + return true; + } + } + gameLocal.Printf("Doesn't contain a %s\n", t.classname); + return false; +} + +bool PointerInList(idEntity *target, idEntity **list, int num) { + for (int j=0; j < num; j++ ) { + if (list[j] == target) { + return true; + } + } + return false; +} + +void hhZone::ResetZoneList() { + // Call Leaving for anything previously entered + idEntity *previouslyInZone; + for (int i=0; i < zoneList.Num(); i++ ) { + previouslyInZone = gameLocal.entities[zoneList[i]]; + + if (previouslyInZone) { + EntityLeaving(previouslyInZone); + } + } + zoneList.Clear(); +} + +void hhZone::TriggerAction(idEntity *activator) { + CancelEvents(&EV_DeactivateZone); + // Turn on until all encroachers are gone + BecomeActive(TH_THINK); +} + +void hhZone::ApplyToEncroachers() { + idEntity *touch[ MAX_GENTITIES ]; + idEntity *previouslyInZone; + idEntity *encroacher; + int i, num; + + idBounds clipBounds; + clipBounds.FromTransformedBounds( GetPhysics()->GetBounds(), GetOrigin(), GetAxis() ); + + // Find all encroachers + if (isSimpleBox) { + num = gameLocal.clip.EntitiesTouchingBounds( clipBounds.Expand(slop), MASK_SHOT_BOUNDINGBOX | CONTENTS_PROJECTILE | CONTENTS_TRIGGER, touch, MAX_GENTITIES ); // CONTENTS_TRIGGER for walkthrough movables + } + else { + num = hhUtils::EntitiesTouchingClipmodel( GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, MASK_SHOT_BOUNDINGBOX | CONTENTS_TRIGGER ); + } + + // for anything previously applied, but no longer encroaching, call EntityLeaving() + for (i=0; i < zoneList.Num(); i++ ) { + previouslyInZone = gameLocal.entities[zoneList[i]]; + + if (previouslyInZone) { + if (!ValidEntity(previouslyInZone) || !PointerInList(previouslyInZone, touch, num)) { + // We've applied before, but it's no longer encroaching + EntityLeaving(previouslyInZone); + + // NOTE: Rather than removing and dealing with the list shifting, we reconstruct the list + // from the touch list later + } + } + } + + // Check touch list for any newly entered encroachers + for (i = 0; i < num; i++ ) { + encroacher = touch[i]; + if (ValidEntity(encroacher)) { + if (zoneList.FindIndex(encroacher->entityNumber) == -1) { + EntityEntered(encroacher); + } + } + } + + // Call all encroachers and rebuild list + zoneList.Clear(); //fixme: could make a version of clear() that doesn't deallocate the memory + for (i = 0; i < num; i++ ) { + encroacher = touch[i]; + if (ValidEntity(encroacher)) { + zoneList.Append(encroacher->entityNumber); + EntityEncroaching(encroacher); + } + } + + // Deactivate if no encroachers left + if (!zoneList.Num()) { + Empty(); +#if !ZONES_ALWAYS_ACTIVE + PostEventMS(&EV_DeactivateZone, 0); +#endif + } +} + +void hhZone::Think() { + if (thinkFlags & TH_THINK) { + ApplyToEncroachers(); + } +} + +void hhZone::Event_TurnOff() { + BecomeInactive(TH_THINK); + bActive = false; +} + +void hhZone::Event_Enable( void ) { + hhTrigger::Event_Enable(); + TriggerAction(this); +} + +void hhZone::Event_Disable( void ) { + BecomeInactive(TH_THINK); + ResetZoneList(); + hhTrigger::Event_Disable(); +} + +void hhZone::Event_Touch( idEntity *other, trace_t *trace ) { + CancelEvents(&EV_DeactivateZone); + // Turn on until all encroachers are gone + BecomeActive(TH_THINK); + + bActive = true; +} + + +//----------------------------------------------------------------------- +// +// hhTriggerZone +// +// Zone used for precise trigger/untrigger mechanic. Fires trigger once +// upon a valid entity entering, and again when a valid entity leaves. Also, +// optionally calls a function for each entity in the volume each tick. +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhTriggerZone) +END_CLASS + +void hhTriggerZone::Spawn() { + funcRefInfo.ParseFunctionKeyValue( spawnArgs.GetString("inCallRef") ); +} + +void hhTriggerZone::Save(idSaveGame *savefile) const { + savefile->WriteStaticObject( funcRefInfo ); +} + +void hhTriggerZone::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( funcRefInfo ); +} + +bool hhTriggerZone::ValidEntity(idEntity *ent) { + return (hhZone::ValidEntity(ent) && !IsType(hhProjectile::Type)); +} + +void hhTriggerZone::EntityEntered(idEntity *ent) { + ActivateTargets(ent); +} + +void hhTriggerZone::EntityLeaving(idEntity *ent) { + ActivateTargets(ent); +} + +void hhTriggerZone::EntityEncroaching( idEntity *ent ) { + if (funcRefInfo.GetFunction() != NULL) { + funcRefInfo.SetParm_Entity( ent, 0 ); + funcRefInfo.Verify(); + funcRefInfo.CallFunction( spawnArgs ); + } +} + +//----------------------------------------------------------------------- +// +// hhGravityZoneBase +// +//----------------------------------------------------------------------- + +ABSTRACT_DECLARATION(hhZone, hhGravityZoneBase) +END_CLASS + +void hhGravityZoneBase::Spawn(void) { + bReorient = spawnArgs.GetBool("reorient"); + bShowVector = spawnArgs.GetBool("showVector"); + bKillsMonsters = spawnArgs.GetBool("killmonsters"); + + //rww - avoid dictionary lookup post-spawn + gravityOriginOffset = vec3_origin; + if (spawnArgs.GetVector("override_origin", gravityOriginOffset.ToString(), gravityOriginOffset)) { + gravityOriginOffset -= GetOrigin(); + } + + //rww - sync over network + fl.networkSync = true; +} + +void hhGravityZoneBase::Save(idSaveGame *savefile) const { + savefile->WriteBool(bReorient); + savefile->WriteBool(bKillsMonsters); + savefile->WriteBool(bShowVector); + savefile->WriteVec3(gravityOriginOffset); +} + +void hhGravityZoneBase::Restore( idRestoreGame *savefile ) { + savefile->ReadBool(bReorient); + savefile->ReadBool(bKillsMonsters); + savefile->ReadBool(bShowVector); + savefile->ReadVec3(gravityOriginOffset); +} + +//rww - network code +void hhGravityZoneBase::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(bReorient, 1); + msg.WriteFloat(slop); +} + +void hhGravityZoneBase::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bReorient = !!msg.ReadBits(1); + slop = msg.ReadFloat(); +} + +void hhGravityZoneBase::ClientPredictionThink( void ) { + Think(); +} +//rww - end network code + +const idVec3 hhGravityZoneBase::GetGravityOrigin() const { + return GetOrigin()+gravityOriginOffset; +} + +bool hhGravityZoneBase::ValidEntity(idEntity *ent) { + if (!ent) { + return false; + } + if (ent->fl.ignoreGravityZones) { + return false; + } + + if (ent->IsType(hhProjectile::Type) && ent->GetPhysics()->GetGravity() == vec3_origin) { + return false; // Projectiles with zero gravity + } + if (ent->IsType(hhPlayer::Type)) { + hhPlayer *pl = static_cast(ent); + if (pl->noclip) { + return false; // Noclipping players + } + else if (pl->spectating) { + return false; //spectating players + } + else if (gameLocal.isMultiplayer && ent->health <= 0) { + return false; //dead mp players (only the prox ragdoll needs gravity) + } + } + if (ent->IsType(hhVehicle::Type)) { + if (static_cast(ent)->IsNoClipping()) { + return false; // Noclipping vehicles + } + if (ent->IsType(hhShuttle::Type) && static_cast(ent)->IsConsole()) { + return false; // unpiloted shuttles + } + } + if (ent->IsType(hhPortal::Type) ) { + return true; // Portals are always valid entities in zones + } + + if (!hhZone::ValidEntity( ent )) { + return false; + } + + return true; +} + +bool hhGravityZoneBase::TouchingOtherZones(idEntity *ent, bool traceCheck, idVec3 &otherInfluence) { //rww + if (!ent->GetPhysics()) { + return false; + } + + bool hitAny = false; + + otherInfluence.Zero(); + + idBounds clipBounds; + + idEntity *touch[ MAX_GENTITIES ]; + clipBounds.FromTransformedBounds( ent->GetPhysics()->GetBounds(), ent->GetOrigin(), ent->GetAxis() ); + int num = gameLocal.clip.EntitiesTouchingBounds( clipBounds, GetPhysics()->GetContents(), touch, MAX_GENTITIES ); + for (int i = 0; i < num; i++) { + if (touch[i] && touch[i]->entityNumber != entityNumber && touch[i]->IsType(hhGravityZoneBase::Type)) { + //touching the object, isn't me, and seems to be another gravity zone + bool touchValid = false; + + if (traceCheck) { //let's perform a trace from the ent's origin to see which zone is hit first. (this is not an ideal solution, but it works) + trace_t tr; + const int checkContents = GetPhysics()->GetContents(); + const idVec3 &start = ent->GetOrigin(); + const float testLength = 512.0f; + idVec3 end; + + //first trace against the other + end = (touch[i]->GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength; + gameLocal.clip.TracePoint(tr, start, end, checkContents, ent); + if (tr.c.entityNum == touch[i]->entityNumber) { //if the trace actually hit the other one + float otherFrac = tr.fraction; + + //now trace against me + end = (GetPhysics()->GetBounds().GetCenter()-start).Normalize()*testLength; + gameLocal.clip.TracePoint(tr, start, GetPhysics()->GetBounds().GetCenter(), checkContents, ent); + if (tr.c.entityNum != entityNumber || tr.fraction >= otherFrac) { //if the impact was further away (or same, don't want fighting), i lose. + touchValid = true; + } + } + } + else { + touchValid = true; + } + + if (touchValid) { + //accumulate force from other zones + hhGravityZoneBase *zone = static_cast(touch[i]); + if (zone->isSimpleBox || ent->GetPhysics()->ClipContents(zone->GetPhysics()->GetClipModel())) { //if not simple box perform a clip check + idVec3 grav = zone->GetCurrentGravity(ent->GetOrigin()); + hitAny = true; + + grav.Normalize(); + otherInfluence += grav; + + otherInfluence.Normalize(); + } + } + } + } + + return hitAny; +} + +void hhGravityZoneBase::EntityEntered( idEntity *ent ) { + if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) { + ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false ); + } + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } +} + +void hhGravityZoneBase::EntityLeaving( idEntity *ent ) { + if( ent->RespondsTo(EV_ShouldRemainAlignedToAxial) ) { + ent->ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)true ); + } + + // Instead of reseting gravity here, post a message to do it, so if we are transitioning + // to another gravity zone or wallwalk, there won't be any discontinuities + if (gameLocal.isClient && !ent->fl.clientEvents && !ent->fl.clientEntity && ent->IsType(hhProjectile::Type)) { + ent->fl.clientEvents = true; //hackery to let normal projectiles reset their gravity for prediction + ent->PostEventMS( &EV_ResetGravity, 200 ); + ent->fl.clientEvents = false; + } + else { + ent->PostEventMS( &EV_ResetGravity, 200 ); + } +} + +void hhGravityZoneBase::EntityEncroaching( idEntity *ent ) { + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() ); + idVec3 otherGravity; + if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour + float l = curGravity.Normalize(); + curGravity += otherGravity; + curGravity *= l*0.5f; + } + if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) { + ent->SetGravity( curGravity ); + ent->GetPhysics()->Activate(); + } + else { + ent->SetGravity( curGravity ); + } + + if (ent->IsType( hhMonsterAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !static_cast(ent)->OverrideKilledByGravityZones() && + !ent->IsType(hhCrawler::Type) && + (idMath::Fabs(curGravity.x) > 0.01f || idMath::Fabs(curGravity.y) > 0.01f || curGravity.z >= 0.0f) && + static_cast(ent)->IsActive() ) { + + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } +} + + +//----------------------------------------------------------------------- +// +// hhGravityZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneBase, hhGravityZone) + EVENT( EV_SetGravityVector, hhGravityZone::Event_SetNewGravity ) +END_CLASS + +void hhGravityZone::Spawn(void) { + zeroGravOnChange = spawnArgs.GetBool("zeroGravOnChange"); + idVec3 startGravity( spawnArgs.GetVector("gravity") ); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + gravityInterpolator.Init( gameLocal.time, 0, startGravity, startGravity ); + + if (startGravity != gameLocal.GetGravity()) { + if (!gameLocal.isMultiplayer) { //don't play sound in mp + StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true); + } + } +} + +void hhGravityZone::Save(idSaveGame *savefile) const { + savefile->WriteFloat( gravityInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( gravityInterpolator.GetDuration() ); + savefile->WriteVec3( gravityInterpolator.GetStartValue() ); + savefile->WriteVec3( gravityInterpolator.GetEndValue() ); + + savefile->WriteInt(interpolationTime); + savefile->WriteBool(zeroGravOnChange); +} + +void hhGravityZone::Restore( idRestoreGame *savefile ) { + float set; + idVec3 vec; + + savefile->ReadFloat( set ); // idInterpolate + gravityInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + gravityInterpolator.SetDuration( set ); + savefile->ReadVec3( vec ); + gravityInterpolator.SetStartValue( vec ); + savefile->ReadVec3( vec ); + gravityInterpolator.SetEndValue( vec ); + + savefile->ReadInt(interpolationTime); + savefile->ReadBool(zeroGravOnChange); +} + +void hhGravityZone::Think() { + hhGravityZoneBase::Think(); + if (thinkFlags & TH_THINK) { + if (bShowVector) { + gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin + GetCurrentGravity(vec3_origin), 10); + } + } +} + +const idVec3 hhGravityZone::GetDestinationGravity() const { + return gravityInterpolator.GetEndValue(); +} + +const idVec3 hhGravityZone::GetCurrentGravity(const idVec3 &location) const { + return gravityInterpolator.GetCurrentValue( gameLocal.time ); +} + +void hhGravityZone::SetGravityOnZone( idVec3 &newGravity ) { + idVec3 startGrav; + + if (!gameLocal.isMultiplayer) { //don't play sound in mp + if ( newGravity.Compare(gameLocal.GetGravity(), VECTOR_EPSILON) ) { + StartSound("snd_gravity_off", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_MISC1, true); + StartSound("snd_gravity_loop_off", SND_CHANNEL_MISC1, 0, true); + } + else { + StartSound("snd_gravity_on", SND_CHANNEL_ANY); + StopSound(SND_CHANNEL_MISC1, true); + StartSound("snd_gravity_loop_on", SND_CHANNEL_MISC1, 0, true); + } + } + + if ( zeroGravOnChange ) { // nla + startGrav = vec3_origin; + } + else { + startGrav = GetCurrentGravity(vec3_origin); + } + // Interpolate to new gravity + gravityInterpolator.Init( gameLocal.time, interpolationTime, startGrav, newGravity ); +} + +//rww - network code +void hhGravityZone::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhGravityZoneBase::WriteToSnapshot(msg); + + msg.WriteFloat(gravityInterpolator.GetStartTime()); + msg.WriteFloat(gravityInterpolator.GetDuration()); + idVec3 vecStart = gravityInterpolator.GetStartValue(); + msg.WriteFloat(vecStart.x); + msg.WriteFloat(vecStart.y); + msg.WriteFloat(vecStart.z); + idVec3 vecEnd = gravityInterpolator.GetEndValue(); + msg.WriteDeltaFloat(vecStart.x, vecEnd.x); + msg.WriteDeltaFloat(vecStart.y, vecEnd.y); + msg.WriteDeltaFloat(vecStart.z, vecEnd.z); +} + +void hhGravityZone::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhGravityZoneBase::ReadFromSnapshot(msg); + gravityInterpolator.SetStartTime(msg.ReadFloat()); + gravityInterpolator.SetDuration(msg.ReadFloat()); + idVec3 vecStart; + vecStart.x = msg.ReadFloat(); + vecStart.y = msg.ReadFloat(); + vecStart.z = msg.ReadFloat(); + gravityInterpolator.SetStartValue(vecStart); + idVec3 vecEnd; + vecEnd.x = msg.ReadDeltaFloat(vecStart.x); + vecEnd.y = msg.ReadDeltaFloat(vecStart.y); + vecEnd.z = msg.ReadDeltaFloat(vecStart.z); + gravityInterpolator.SetEndValue(vecEnd); +} + +void hhGravityZone::ClientPredictionThink( void ) { + hhGravityZoneBase::ClientPredictionThink(); +} +//rww - end network code + +void hhGravityZone::Event_SetNewGravity( idVec3 &newGravity ) { + SetGravityOnZone( newGravity ); +} + + +//----------------------------------------------------------------------- +// +// hhGravityZoneInward +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneBase, hhGravityZoneInward) + EVENT( EV_SetGravityFactor, hhGravityZoneInward::Event_SetNewGravityFactor ) +END_CLASS + +void hhGravityZoneInward::Spawn(void) { + float startFactor = spawnArgs.GetFloat("factor", "50000"); + monsterGravityFactor = spawnArgs.GetFloat("monsterGravFactor", "1"); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + factorInterpolator.Init( gameLocal.time, 0, startFactor, startFactor ); +} + +void hhGravityZoneInward::Save(idSaveGame *savefile) const { + savefile->WriteFloat( factorInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( factorInterpolator.GetDuration() ); + savefile->WriteFloat( factorInterpolator.GetStartValue() ); + savefile->WriteFloat( factorInterpolator.GetEndValue() ); + + savefile->WriteInt(interpolationTime); + savefile->WriteFloat(monsterGravityFactor); +} + +void hhGravityZoneInward::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + factorInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + factorInterpolator.SetDuration( set ); + savefile->ReadFloat( set ); + factorInterpolator.SetStartValue(set); + savefile->ReadFloat( set ); + factorInterpolator.SetEndValue( set ); + + savefile->ReadInt(interpolationTime); + savefile->ReadFloat(monsterGravityFactor); +} + +void hhGravityZoneInward::EntityEntered(idEntity *ent) { + hhGravityZoneBase::EntityEntered(ent); + if ( ent && ent->IsType( hhMonsterAI::Type ) ) { + static_cast(ent)->GravClipModelAxis( true ); + } + // Disallow slope checking, it makes us stutter when walking on convex surfaces + + // aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone + //Didn't see any studdering when thia was commented out. Do we still need it? + //if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + // static_cast(ent->GetPhysics())->SetSlopeCheck(false); + //} + //now done constantly while in a gravity zone + /* + if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(1); + } + */ +} + +void hhGravityZoneInward::EntityLeaving(idEntity *ent) { + hhGravityZoneBase::EntityLeaving(ent); + if ( ent && ent->IsType( hhMonsterAI::Type ) ) { + static_cast(ent)->GravClipModelAxis( false ); + } + // Re-enable slope checking + + // aob - commented this because it allows the player to walk up vertical walls while in inward gravity zone + //Didn't see any studdering when thia was commented out. Do we still need it? + //if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + // static_cast(ent->GetPhysics())->SetSlopeCheck(true); + //} + if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(0); + } +} + +// This is actually called each tick if for entities inside +void hhGravityZoneInward::EntityEncroaching( idEntity *ent ) { + + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + idVec3 curGravity = GetCurrentGravity( ent->GetOrigin() ); + idVec3 otherGravity; + if (TouchingOtherZones(ent, false, otherGravity)) { //factor in gravity for all other zones being touched to avoid back-and-forth behaviour + float l = curGravity.Normalize(); + curGravity += otherGravity; + curGravity *= l*0.5f; + } + if (ent->GetPhysics()->IsAtRest() && ent->GetGravity() != curGravity) { + ent->SetGravity( curGravity ); + ent->GetPhysics()->Activate(); + } + else { + ent->SetGravity( curGravity ); + } + if (ent->IsType( idAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !ent->IsType(hhCrawler::Type) && + (curGravity.x != 0.0f || curGravity.y != 0.0f || curGravity.z >= 0.0f) && + !static_cast(ent)->OverrideKilledByGravityZones() && + static_cast(ent)->IsActive() ) { + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } + //rww + else if (ent->IsType( hhPlayer::Type ) && ent->GetPhysics()->IsType(hhPhysics_Player::Type) ) { + static_cast(ent->GetPhysics())->SetInwardGravity(1); + } + + + if( ent->IsType(idAI::Type) && ent->health > 0 ) { + ent->GetPhysics()->SetGravity( ent->GetPhysics()->GetGravity() * monsterGravityFactor ); + ent->GetPhysics()->Activate(); + } + + if( bShowVector ) { + hhUtils::DebugCross( colorBlue, GetOrigin(), 100, 10 ); + idVec3 newGrav = GetCurrentGravity( ent->GetOrigin() ); + gameRenderWorld->DebugArrow( colorGreen, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->origin + newGrav, 10 ); + } +} + +const idVec3 hhGravityZoneInward::GetCurrentGravity( const idVec3 &location ) const { + idVec3 grav; + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() ); + idVec3 inward = origin - location; + inward.Normalize(); + grav = inward * DEFAULT_GRAVITY * factor; + return grav; +} + +void hhGravityZoneInward::Event_SetNewGravityFactor( float newFactor ) { + // Interpolate to new gravity factor + float curFactor = factorInterpolator.GetCurrentValue( gameLocal.GetTime() ); + factorInterpolator.Init( gameLocal.GetTime(), interpolationTime, curFactor, newFactor ); +} + +//----------------------------------------------------------------------- +// +// hhAIWallwalkZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZone, hhAIWallwalkZone) +END_CLASS + +bool hhAIWallwalkZone::ValidEntity(idEntity *ent) { + // allow AI that isnt dead + return ent->IsType(idAI::Type) && ent->health > 0; +} + +void hhAIWallwalkZone::EntityEncroaching( idEntity *ent ) { + // Cancel any pending gravity resets from other zones + ent->CancelEvents( &EV_ResetGravity ); + + trace_t TraceInfo; + gameLocal.clip.TracePoint(TraceInfo, ent->GetOrigin(), ent->GetOrigin() + (idVec3(0,0,-300)*ent->GetRenderEntity()->axis), ent->GetPhysics()->GetClipMask(), ent); + if( TraceInfo.fraction < 1.0f ) { // && ent->health > 0 ) { + ent->SetGravity( -TraceInfo.c.normal ); + ent->GetPhysics()->Activate(); + } +} + +//----------------------------------------------------------------------- +// +// hhGravityZoneSinkhole +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhGravityZoneInward, hhGravityZoneSinkhole) + EVENT( EV_SetGravityFactor, hhGravityZoneSinkhole::Event_SetNewGravityFactor ) +END_CLASS + +void hhGravityZoneSinkhole::Spawn(void) { + bReorient = false; + maxMagnitude = spawnArgs.GetFloat("maxMagnitude", "10000"); + minMagnitude = spawnArgs.GetFloat("minMagnitude", "0"); +} + +void hhGravityZoneSinkhole::Save(idSaveGame *savefile) const { + savefile->WriteFloat(maxMagnitude); + savefile->WriteFloat(minMagnitude); +} + +void hhGravityZoneSinkhole::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat(maxMagnitude); + savefile->ReadFloat(minMagnitude); +} + +// Still have this in case we want to do something mass based +const idVec3 hhGravityZoneSinkhole::GetCurrentGravityEntity(const idEntity *ent) const { + idVec3 grav = vec3_origin; + if (ent) { + // precalc mass product / G as a constant and expose that as the fudge factor + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.time ); + idVec3 inward = origin - ent->GetOrigin(); + float distance = inward.Normalize(); + float distanceSquared = distance*distance; + + // Some different gravitational fields + // float gravMag = (mass * ent->GetPhysics()->GetMass() * GRAVITATIONAL_CONSTANT) / distanceSquared; + // float gravMag = factor*factor / distanceSquared; // Inverse squared distance + // float gravMag = factor / sqrt(distance); // Inverse sqrt distance + // float gravMag = factor * sqrt(distance); // sqrt distance + float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance + //gameLocal.Printf("factor=%.0f gravity magnitude=%.2f\n", factor, gravMag); + + gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces + grav = inward * gravMag; + } + return grav; +} + +const idVec3 hhGravityZoneSinkhole::GetCurrentGravity(const idVec3 &location) const { + idVec3 grav; + + // precalc mass product / G as a constant and expose that as the fudge factor + idVec3 origin = GetGravityOrigin(); + float factor = factorInterpolator.GetCurrentValue( gameLocal.time ); + idVec3 inward = origin - location; + float distance = inward.Normalize(); + float distanceSquared = distance*distance; + + // Some different gravitational fields + float gravMag = factor*factor / 2 + 0.2f * distanceSquared; // Inverse squared distance + gravMag = hhMath::ClampFloat(minMagnitude, maxMagnitude, gravMag); // This will cut off extremely large forces + grav = inward * gravMag; + return grav; +} + +void hhGravityZoneSinkhole::Event_SetNewGravityFactor( float newFactor ) { + // Interpolate to new gravity factor + float curFactor = factorInterpolator.GetCurrentValue(gameLocal.time); + factorInterpolator.Init( gameLocal.time, interpolationTime, curFactor, newFactor ); +} + + +//----------------------------------------------------------------------- +// +// hhVelocityZone +// +//----------------------------------------------------------------------- + +const idEventDef EV_SetVelocityVector("setvelocity", "v"); + +CLASS_DECLARATION(hhZone, hhVelocityZone) + EVENT( EV_SetVelocityVector, hhVelocityZone::Event_SetNewVelocity ) +END_CLASS + +void hhVelocityZone::Spawn(void) { + bReorient = spawnArgs.GetBool("reorient"); + interpolationTime = SEC2MS(spawnArgs.GetFloat("interpTime")); + idVec3 startVelocity = spawnArgs.GetVector("velocity"); + //slop = 25.0f; // we use a slightly larger bounds to catch things that are rotated by bReorient + bShowVector = spawnArgs.GetBool("showVector"); + bKillsMonsters = spawnArgs.GetBool("killmonsters"); + + velocityInterpolator.Init(gameLocal.time, 0, startVelocity, startVelocity); +} + +void hhVelocityZone::Save(idSaveGame *savefile) const { + savefile->WriteFloat( velocityInterpolator.GetStartTime() ); // idInterpolate + savefile->WriteFloat( velocityInterpolator.GetDuration() ); + savefile->WriteVec3( velocityInterpolator.GetStartValue() ); + savefile->WriteVec3( velocityInterpolator.GetEndValue() ); + + savefile->WriteBool(bKillsMonsters); + savefile->WriteBool(bReorient); + savefile->WriteBool(bShowVector); + savefile->WriteInt(interpolationTime); +} + +void hhVelocityZone::Restore( idRestoreGame *savefile ) { + float set; + idVec3 vec; + + savefile->ReadFloat( set ); // idInterpolate + velocityInterpolator.SetStartTime( set ); + savefile->ReadFloat( set ); + velocityInterpolator.SetDuration( set ); + savefile->ReadVec3( vec ); + velocityInterpolator.SetStartValue( vec ); + savefile->ReadVec3( vec ); + velocityInterpolator.SetEndValue( vec ); + + savefile->ReadBool(bKillsMonsters); + savefile->ReadBool(bReorient); + savefile->ReadBool(bShowVector); + savefile->ReadInt(interpolationTime); +} + +void hhVelocityZone::EntityLeaving(idEntity *ent) { + ent->GetPhysics()->SetLinearVelocity(idVec3(0, 0, 0)); + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } +} + +void hhVelocityZone::EntityEncroaching(idEntity *ent) { + idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + idVec3 baseVelocityDirection = baseVelocity; + baseVelocityDirection.Normalize(); + + idVec3 curVelocity; + curVelocity = ent->GetPhysics()->GetLinearVelocity(); + curVelocity.ProjectOntoPlane(baseVelocityDirection); + + ent->GetPhysics()->SetLinearVelocity( curVelocity + baseVelocity ); + if( ent->RespondsTo(EV_OrientToGravity) ) { + ent->ProcessEvent( &EV_OrientToGravity, (int)bReorient ); + } + else if (ent->IsType( idAI::Type )) { + if (bKillsMonsters && ent->health > 0 && + !static_cast(ent)->IsFlying()) { + const char *monsterDamageType = spawnArgs.GetString("def_monsterdamage"); + ent->Damage(this, NULL, vec3_origin, monsterDamageType, 1.0f, 0); + } + } +} + +void hhVelocityZone::Think() { + hhZone::Think(); + if (thinkFlags & TH_THINK) { + if (bShowVector) { + idVec3 baseVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + gameRenderWorld->DebugArrow(colorGreen, renderEntity.origin, renderEntity.origin+baseVelocity, 10); + } + } +} + +void hhVelocityZone::Event_SetNewVelocity( idVec3 &newVelocity ) { + // Interpolate to new velocity + idVec3 currentVelocity = velocityInterpolator.GetCurrentValue(gameLocal.time); + velocityInterpolator.Init(gameLocal.time, interpolationTime, currentVelocity, newVelocity); +} + + +//----------------------------------------------------------------------- +// +// hhShuttleRecharge +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleRecharge) +END_CLASS + +void hhShuttleRecharge::Spawn(void) { + amountHealth = spawnArgs.GetInt("amounthealth"); + amountPower = spawnArgs.GetInt("amountpower"); +} + +void hhShuttleRecharge::Save(idSaveGame *savefile) const { + savefile->WriteInt(amountHealth); + savefile->WriteInt(amountPower); +} + +void hhShuttleRecharge::Restore( idRestoreGame *savefile ) { + savefile->ReadInt(amountHealth); + savefile->ReadInt(amountPower); +} + +bool hhShuttleRecharge::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleRecharge::EntityEntered(idEntity *ent) { + //static_cast(ent)->SetRecharging(true); +} + +void hhShuttleRecharge::EntityLeaving(idEntity *ent) { + //static_cast(ent)->SetRecharging(false); +} + +void hhShuttleRecharge::EntityEncroaching(idEntity *ent) { + if (ent->IsType(hhVehicle::Type)) { + hhVehicle *vehicle = static_cast(ent); + + //HUMANHEAD bjk PCF (4-27-06) - shuttle recharge was slow + if(USERCMD_HZ == 30) { + vehicle->GiveHealth(2*amountHealth); + vehicle->GivePower(2*amountPower); + } else { + vehicle->GiveHealth(amountHealth); + vehicle->GivePower(amountPower); + } + } +} + + +//----------------------------------------------------------------------- +// +// hhDockingZone +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhDockingZone) +END_CLASS + +void hhDockingZone::Spawn(void) { + dock = NULL; + triggerBehavior = TB_PLAYER_MONSTERS_FRIENDLIES; // Allow all actors to trigger it + + fl.networkSync = true; +} + +void hhDockingZone::Save(idSaveGame *savefile) const { + dock.Save(savefile); +} + +void hhDockingZone::Restore( idRestoreGame *savefile ) { + dock.Restore(savefile); +} + +void hhDockingZone::RegisterDock(hhDock *d) { + dock = d; +} + +bool hhDockingZone::ValidEntity(idEntity *ent) { + if (ent) { + if (dock.IsValid() && dock->ValidEntity(ent)) { + return true; + } + if (ent->IsType(idActor::Type)) { //FIXME: Is this causing the shuttleCount to go wrong + return hhShuttle::ValidPilot(static_cast(ent)); + } + } + return false; +} + +void hhDockingZone::EntityEncroaching(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityEncroaching(ent); + } +} + +void hhDockingZone::EntityEntered(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityEntered(ent); + } +} + +void hhDockingZone::EntityLeaving(idEntity *ent) { + if (dock.IsValid()) { + dock->EntityLeaving(ent); + } +} + +void hhDockingZone::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(dock.GetSpawnId(), 32); +} + +void hhDockingZone::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + dock.SetSpawnId(msg.ReadBits(32)); +} + +void hhDockingZone::ClientPredictionThink( void ) { + if (!gameLocal.isNewFrame) { + return; + } + Think(); +} + +//----------------------------------------------------------------------- +// +// hhShuttleDisconnect +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleDisconnect) +END_CLASS + +void hhShuttleDisconnect::Spawn(void) { +} + +bool hhShuttleDisconnect::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleDisconnect::EntityEntered(idEntity *ent) { + static_cast(ent)->AllowTractor(false); +} + +void hhShuttleDisconnect::EntityEncroaching(idEntity *ent) { +} + +void hhShuttleDisconnect::EntityLeaving(idEntity *ent) { + static_cast(ent)->AllowTractor(true); +} + + +//----------------------------------------------------------------------- +// +// hhShuttleSlingshot +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhShuttleSlingshot) +END_CLASS + +void hhShuttleSlingshot::Spawn(void) { +} + +bool hhShuttleSlingshot::ValidEntity(idEntity *ent) { + return ent && ent->IsType(hhShuttle::Type); +} + +void hhShuttleSlingshot::EntityEntered(idEntity *ent) { +} + +void hhShuttleSlingshot::EntityEncroaching(idEntity *ent) { + float factor = spawnArgs.GetFloat("BoostFactor"); + + hhShuttle *shuttle = static_cast(ent); + shuttle->ApplyBoost( 255.0f * factor); + + // CJR: Alter the player's view when zooming through a slingshot zone + shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, spawnArgs.GetString( "mtr_speedView" ), -1, false ); + +} + +void hhShuttleSlingshot::EntityLeaving(idEntity *ent) { + // CJR: Reset the player's view after zooming through a slingshot zone + hhShuttle *shuttle = static_cast(ent); + shuttle->GetPilot()->PostEventMS( &EV_SetOverlayMaterial, 0, "", -1, false ); +} + + + +//----------------------------------------------------------------------- +// +// hhRemovalVolume +// +//----------------------------------------------------------------------- + +CLASS_DECLARATION(hhZone, hhRemovalVolume) +END_CLASS + +void hhRemovalVolume::Spawn(void) { +} + +bool hhRemovalVolume::ValidEntity(idEntity *ent) { + return ent && ( + ent->IsType(idMoveable::Type) || + ent->IsType(idItem::Type) || + ent->IsType(hhAFEntity::Type) || + ent->IsType(hhAFEntity_WithAttachedHead::Type) || + (ent->IsType(hhMonsterAI::Type) && ent->health<=0 && !ent->fl.isTractored) ); +} + +void hhRemovalVolume::EntityEntered(idEntity *ent) { + ent->PostEventMS(&EV_Remove, 0); +} + +void hhRemovalVolume::EntityEncroaching(idEntity *ent) { +} + +void hhRemovalVolume::EntityLeaving(idEntity *ent) { +} diff --git a/neo/prey/Prey/game_zone.h b/neo/prey/Prey/game_zone.h new file mode 100644 index 00000000..e830ea28 --- /dev/null +++ b/neo/prey/Prey/game_zone.h @@ -0,0 +1,266 @@ + +#ifndef __GAME_GRAVITYZONE_H__ +#define __GAME_GRAVITYZONE_H__ + +class hhDock; + +class hhZone : public hhTrigger { +public: + ABSTRACT_PROTOTYPE( hhZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Present( void ) { } // HUMANHEAD mdl: Not used by zones + + // hhTrigger interface + void TriggerAction(idEntity *activator); + + // hhZone interface + void ResetZoneList(); + void ApplyToEncroachers(); + bool ContainsEntityOfType(const idTypeInfo &t); + virtual void EntityEntered(idEntity *ent) {} + virtual void EntityLeaving(idEntity *ent) {} + virtual void EntityEncroaching(idEntity *ent) {} + virtual bool ValidEntity(idEntity *ent); + virtual void Empty(); + +protected: + void Event_TurnOff(); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_Touch( idEntity *other, trace_t *trace ); + +protected: + idList zoneList; // List of valid entities in zone last frame + float slop; +}; + +class hhTriggerZone : public hhZone { +public: + CLASS_PROTOTYPE( hhTriggerZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + + hhFuncParmAccessor funcRefInfo; +}; + +class hhGravityZoneBase : public hhZone { +public: + ABSTRACT_PROTOTYPE( hhGravityZoneBase ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual const idVec3 GetGravityOrigin() const; + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const = 0; + + virtual bool TouchingOtherZones(idEntity *ent, bool traceCheck, idVec3 &otherInfluence); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + bool bReorient; + bool bKillsMonsters; + bool bShowVector; + idVec3 gravityOriginOffset; //rww - avoid dictionary lookup +}; + +class hhGravityZone : public hhGravityZoneBase { +public: + CLASS_PROTOTYPE( hhGravityZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + virtual const idVec3 GetDestinationGravity() const; + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + virtual void SetGravityOnZone( idVec3 &newGravity ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + void Event_SetNewGravity( idVec3 &newgrav ); + +protected: + idInterpolate gravityInterpolator; + int interpolationTime; + bool zeroGravOnChange; +}; + +class hhAIWallwalkZone : public hhGravityZone { +public: + CLASS_PROTOTYPE( hhAIWallwalkZone ); + virtual void EntityEncroaching(idEntity *ent); + virtual bool ValidEntity(idEntity *ent); +}; + +class hhGravityZoneInward : public hhGravityZoneBase { +public: + CLASS_PROTOTYPE( hhGravityZoneInward ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + +protected: + virtual void Event_SetNewGravityFactor( float newFactor ); + +protected: + idInterpolate factorInterpolator; + int interpolationTime; + float monsterGravityFactor; +}; + + +#define GRAVITATIONAL_CONSTANT 1.03416206832413664e-7f + +class hhGravityZoneSinkhole : public hhGravityZoneInward { +public: + CLASS_PROTOTYPE( hhGravityZoneSinkhole ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual const idVec3 GetCurrentGravity(const idVec3 &location) const; + const idVec3 GetCurrentGravityEntity(const idEntity *ent) const; + +protected: + void Event_SetNewGravityFactor( float newFactor ); + +protected: + float maxMagnitude; + float minMagnitude; +}; + + +class hhVelocityZone : public hhZone { +public: + CLASS_PROTOTYPE( hhVelocityZone ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: + void Event_SetNewVelocity( idVec3 &newvel ); + +protected: + idInterpolate velocityInterpolator; + bool bKillsMonsters; + bool bReorient; + bool bShowVector; + int interpolationTime; +}; + +class hhShuttleRecharge : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleRecharge ); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + +protected: + int amountHealth; + int amountPower; +}; + +class hhDockingZone : public hhZone { +public: + CLASS_PROTOTYPE( hhDockingZone ); + + void Spawn(void); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + void RegisterDock(hhDock *d); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +protected: + idEntityPtr dock; +}; + +class hhShuttleDisconnect : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleDisconnect ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +class hhShuttleSlingshot : public hhZone { +public: + CLASS_PROTOTYPE( hhShuttleSlingshot ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +class hhRemovalVolume : public hhZone { +public: + CLASS_PROTOTYPE( hhRemovalVolume ); + + void Spawn(void); + virtual bool ValidEntity(idEntity *ent); + virtual void EntityEntered(idEntity *ent); + virtual void EntityEncroaching(idEntity *ent); + virtual void EntityLeaving(idEntity *ent); + +protected: +}; + +#endif /* __GAME_GRAVITYZONE_H__ */ diff --git a/neo/prey/Prey/particles_particles.cpp b/neo/prey/Prey/particles_particles.cpp new file mode 100644 index 00000000..d1cf1c5c --- /dev/null +++ b/neo/prey/Prey/particles_particles.cpp @@ -0,0 +1,368 @@ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idVec3 hhSmokeParticles::defaultDir( 0.0f, 0.0f, 1.0f ); + +/* +================ +hhSmokeParticles::hhSmokeParticles +================ +*/ +hhSmokeParticles::hhSmokeParticles() { +} + +#if 0 +/* +================ +hhParticleSystem::RunTrailStage +================ +*/ +void hhParticleSystem::RunTrailStage( const int ev ) { + if (GetParticle()) { + idFXSmokeStage *stage = particleSystem->events[ev]; + float fraction; + //HUMANHEAD: aob + int numParticleToTrigger = 0; + idVec3 gravity; + //HUMANHEAD END + + for( int i=0; ihidden && current && current->state != SMOKE_DEAD ) { + // do we need to insert another particle? + if (current->nozzle && current->triggered != current->numToTrigger) { + //HUMANHEAD: aob - moved logic to helper function + ResetSmokeParticleTrail( ¤t->particles[current->triggered], current, stage ); + //HUMANHEAD END + current->triggered++; + } + // move them all, kill if we need to + bool triggerAlive = false; + for( int j=0; jtriggered; j++ ) { + if ( current->particles[j].age <= stage->timeToFade ) { + smokeParticle *sParticle = ¤t->particles[j]; + idVec3 speed = vec3_origin; // HUMANHEAD JRM - changed to vec3 + if (sParticle->age >= stage->timeToFinalSpeed) { + speed = stage->finalSpeed; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalSpeed; + speed = stage->initialSpeed + (stage->finalSpeed - stage->initialSpeed) * fraction; + } + //HUMANHEAD: aob + if (sParticle->age >= stage->timeToFinalGravity) { + gravity = stage->finalGravity; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalGravity; + gravity = stage->initialGravity + (stage->finalGravity - stage->initialGravity) * fraction; + } + //HUMANHEAD END + if (sParticle->age != 0) { + if (stage->slowSpawnSpeedOverLife) { + fraction = 1.0f - ((float)j / current->numToTrigger); + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed * fraction, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } else { + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } + //HUMANHEAD: aob + sParticle->position += gravity; + //HUMANHEAD END + } + sParticle->age += gameLocal.msec; + triggerAlive = true; + if ( (sParticle->age+current->baseTime) >= stage->timeToFade ) { + if (current->nozzle && stage->continuous ) { + //HUMANHEAD: aob - moved logic to helper function + ResetSmokeParticleTrail( sParticle, current, stage ); + //HUMANHEAD END + } + } + } + } + if (!triggerAlive) { + current->state = SMOKE_DEAD; + } + } + } + } +} + +/* +================ +hhParticleSystem::ResetSmokeParticleTrail +================ +*/ +void hhParticleSystem::ResetSmokeParticleTrail( smokeParticle* particle, smokeGroup* group, idFXSmokeStage* smokeStage ) { + float ang = 0.0f; + float size = 0.0f; + + particle->position = group->groupOrigin; + particle->direction = group->groupDirection; + // HUMANHEAD JRM - need to make spawn direction spherical - NOT box + ang = gameLocal.random.RandomFloat() * hhMath::TWO_PI; + size = smokeStage->xymove; + if( !smokeStage->noXYMoveRandomness ) { + size *= gameLocal.random.RandomFloat(); + } + particle->xyzmove = idVec3( 0.0f, hhMath::Cos(ang) * size, hhMath::Sin(ang) * size ); + particle->xyzmove *= particle->direction.ToMat3(); + // HUMANHEAD JRM - end + particle->age = 0; +} + +/* +================ +hhParticleSystem::RunSmokeStage +================ +*/ +void hhParticleSystem::RunSmokeStage( const int ev ) { + if (GetParticle()) { + idFXSmokeStage *stage = particleSystem->events[ev]; + if (stage->trails) { + return RunTrailStage( ev ); + } + float fraction; + //HUMANHEAD: aob + int numParticleToTrigger = 0; + idVec3 gravity; + //HUMANHEAD END + for( int i=0; ihidden && current->state != SMOKE_DEAD ) { + // do we need to insert another particle? + // HUMANHEAD JRM - made a while and seperated if + if (current->nozzle && current->frameCount >= current->triggerEvery && current->triggered < current->numToTrigger) { + //HUMANHEAD: aob + numParticleToTrigger = current->minToTrigger + gameLocal.random.RandomInt( current->maxToTrigger - current->minToTrigger ); + //HUMANHEAD END + while (numParticleToTrigger && current->triggered < current->numToTrigger) {//HUMANHEAD: aob - changed numToTrigger to numParticlesToTrigger + //HUMANHEAD: aob - moved logic to helper function + ResetSmokeParticle( ¤t->particles[current->triggered], current, stage ); + current->triggered++; + //HUMANHEAD: aob + --numParticleToTrigger; + //HUMANHEAD END + } + current->triggerEvery = stage->triggerEvery - gameLocal.random.RandomInt( stage->triggerEvery - stage->minTriggerEvery ); + current->frameCount = 0; + } + current->frameCount++; + // move them all, kill if we need to + idVec3 mwpVector; + mwpVector.Zero(); + if (stage->moveWithParent && current->groupOrigin != current->oldGroupOrigin) { + mwpVector = current->groupOrigin - current->oldGroupOrigin; + current->oldGroupOrigin = current->groupOrigin; + } + bool triggerAlive = false; + for( int j=0; jtriggered; j++ ) { + if ( current->particles[j].age <= stage->timeToFade ) { + smokeParticle *sParticle = ¤t->particles[j]; + sParticle->age += gameLocal.msec; + sParticle->rotation += (sParticle->rotationSpeed * stage->rotationMul); + idVec3 speed; // HUMANHEAD JRM - changed to vec3 from float + if (sParticle->age >= stage->timeToFinalSpeed) { + speed = stage->finalSpeed; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalSpeed; + speed = stage->initialSpeed + (stage->finalSpeed - stage->initialSpeed) * fraction; + } + //HUMANHEAD: aob + if (sParticle->age >= stage->timeToFinalGravity) { + gravity = stage->finalGravity; + } else { + fraction = sParticle->age * stage->oneOverTimeToFinalGravity; + gravity = stage->initialGravity + (stage->finalGravity - stage->initialGravity) * fraction; + } + //HUMANHEAD END + if (sParticle->age != 0) { + if (stage->slowSpawnSpeedOverLife) { + fraction = 1.0f - ((float)j / current->numToTrigger); + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed * fraction, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } else { + //HUMANHEAD: aob + sParticle->position += DetermineDeltaPos( speed, sParticle->xyzmove, sParticle->direction ); + //HUMANHEAD END + } + //HUMANHEAD: aob + sParticle->position += gravity; + //HUMANHEAD END + } + sParticle->position += mwpVector; + triggerAlive = true; + if ( sParticle->age >= stage->timeToFade ) { + if( current->nozzle && stage->continuous ) { + //HUMANHEAD: aob - moved logic into helper function + ResetSmokeParticle( sParticle, current, stage ); + //HUMANHEAD END + } + } + } + } + if (!triggerAlive) { + current->state = SMOKE_DEAD; + } + } + } + } +} + +/* +================ +hhParticleSystem::ResetSmokeParticle +================ +*/ +void hhParticleSystem::ResetSmokeParticle( smokeParticle* particle, smokeGroup* group, idFXSmokeStage* smokeStage ) { + float ang = 0.0f; + float size = 0.0f; + + particle->position = (smokeStage->moveWithParent) ? group->oldGroupOrigin : group->groupOrigin; + particle->direction = group->groupDirection; + particle->rotation = gameLocal.random.RandomInt(360); + particle->rotationSpeed = gameLocal.random.CRandomFloat() * 3.0f; + // HUMANHEAD JRM - need to make spawn direction spherical - NOT box + ang = gameLocal.random.RandomFloat() * hhMath::TWO_PI; + size = smokeStage->xymove; + if( !smokeStage->noXYMoveRandomness ) { + size *= gameLocal.random.RandomFloat(); + } + particle->xyzmove = idVec3( 0.0f, hhMath::Cos(ang) * size, hhMath::Sin(ang) * size ); + particle->xyzmove *= particle->direction.ToMat3(); + // HUMANHEAD JRM - end + particle->age = 0; +} + +/* +================ +hhParticleSystem::DetermineDeltaPos +================ +*/ +idVec3 hhParticleSystem::DetermineDeltaPos( const idVec3& speed, const idVec3& xyzmove, const idVec3& dir ) { + idVec3 deltaPos; + //Convert to world coords + idVec3 velocity = (dir + xyzmove) * dir.ToMat3().Transpose(); + + for( int ix = 0; ix < 3; ++ix ) { + deltaPos[ix] = speed[ix] * velocity[ix]; + } + + //Convert back to local coords + return deltaPos * dir.ToMat3(); +} + +/* +================ +hhParticleSystem::ReTrigger +================ +*/ +const int hhParticleSystem::ReTrigger( const idVec3& newOrigin, const idVec3& newDirection ) { + + // all triggers need to be checked, but all triggers have the same Num() <-- optimize + for( int i = 0; i < triggers[0].Num(); i++ ) { + if ( StageFinished(i) ) { + ResetStage(i, newOrigin, newDirection ); + return (myIndex<<16)|i; + } + } + + smokeParticle initial; + initial.position = vec3_origin; + initial.rotation = 0.0f; + initial.rotationSpeed = 0.0f; + initial.direction = vec3_origin; + //HUMANHEAD: aob + initial.xyzmove.Zero(); + //HUMANHEAD END + + GetParticle(); + for( int i=0; ievents.Num(); i++ ) { + smokeGroup* newGroup = new smokeGroup; + + newGroup->state = SMOKE_ALIVE; + //HUMANHEAD: aob + newGroup->triggered = 0; + newGroup->minToTrigger = particleSystem->events[i]->minToTrigger; + newGroup->maxToTrigger = particleSystem->events[i]->maxToTrigger; + newGroup->numToTrigger = (particleSystem->events[i]->randomNumToTrigger) ? newGroup->minToTrigger + gameLocal.random.RandomInt(newGroup->maxToTrigger - newGroup->minToTrigger) : particleSystem->events[i]->numToTrigger; + newGroup->particles.AssureSize( newGroup->numToTrigger, initial ); + //HUMANHEAD END + newGroup->triggerEvery = particleSystem->events[i]->triggerEvery - gameLocal.random.RandomInt( particleSystem->events[i]->triggerEvery - particleSystem->events[i]->minTriggerEvery ); + newGroup->frameCount = newGroup->triggerEvery; + //HUMANHEAD: aob + newGroup->groupDirection = (newDirection == vec3_origin) ? defaultDir : newDirection; + //HUMANHEAD END + newGroup->groupOrigin = newOrigin; + newGroup->oldGroupOrigin = newOrigin; + newGroup->nozzle = true; + newGroup->hidden = false; + newGroup->baseTime = 0; + triggers[i].Append( newGroup ); + } + return (myIndex<<16)|(triggers[0].Num()-1); +} + +/* +================ +hhParticleSystem::ResetStage +================ +*/ +void hhParticleSystem::ResetStage( const int st, const idVec3 &start, const idVec3& direction ) { + GetParticle(); + + //HUMANHEAD: aob + smokeParticle initial; + initial.position = vec3_origin; + initial.rotation = 0.0f; + initial.rotationSpeed = 0.0f; + initial.direction = vec3_origin; + initial.xyzmove.Zero(); + //HUMANHEAD END + + for( int i=0; ievents.Num(); i++ ) { + smokeGroup* current = triggers[i][st]; + current->state = SMOKE_ALIVE; + current->triggered = 0; + //HUMANHEAD: aob + current->minToTrigger = particleSystem->events[i]->minToTrigger; + current->maxToTrigger = particleSystem->events[i]->maxToTrigger; + current->numToTrigger = (particleSystem->events[i]->randomNumToTrigger) ? current->minToTrigger + gameLocal.random.RandomInt(current->maxToTrigger - current->minToTrigger) : particleSystem->events[i]->numToTrigger; + current->particles.AssureSize( current->numToTrigger, initial ); + //HUMANHEAD END + current->triggerEvery = particleSystem->events[i]->triggerEvery - gameLocal.random.RandomInt( particleSystem->events[i]->triggerEvery - particleSystem->events[i]->minTriggerEvery ); + current->frameCount = current->triggerEvery; + current->groupOrigin = start; + current->oldGroupOrigin = start; + //HUMANHEAD: aob + current->groupDirection = (direction == vec3_origin) ? defaultDir : direction; + //HUMANHEAD END + current->nozzle = true; + current->hidden = false; + current->baseTime = 0; + } +} + +/* +================ +hhParticleSystem::SetNozzleOrigin +================ +*/ +void hhParticleSystem::SetNozzleDirection( const int handle, const idVec3 &vec ) { + int st = TriggerForHandle( handle ); + GetParticle(); + for( int i=0; ievents.Num(); i++ ) { + smokeGroup* current = triggers[i][st]; + //HUMANHEAD: aob + current->groupDirection = (vec == vec3_origin) ? defaultDir : vec; + //HUMANHEAD END + } +} +#endif \ No newline at end of file diff --git a/neo/prey/Prey/particles_particles.h b/neo/prey/Prey/particles_particles.h new file mode 100644 index 00000000..5498d04b --- /dev/null +++ b/neo/prey/Prey/particles_particles.h @@ -0,0 +1,13 @@ +// COMMENTED OUT +#ifndef __PREY_PARTICLES_PARTICLES__ +#define __PREY_PARTICLES_PARTICLES__ + +class hhSmokeParticles : public idSmokeParticles { + public: + hhSmokeParticles(); + + protected: + static const idVec3 defaultDir; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/physics_delta.cpp b/neo/prey/Prey/physics_delta.cpp new file mode 100644 index 00000000..8f97b404 --- /dev/null +++ b/neo/prey/Prey/physics_delta.cpp @@ -0,0 +1,155 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idPhysics_Actor, hhPhysics_Delta ) +END_CLASS + +//============ +// hhPhysics_Delta::hhPhysics_Delta +//============ +hhPhysics_Delta::hhPhysics_Delta() { + + delta.Zero(); +} //. hhPhysics_Delta::hhPhysics_Delta() + + +//============ +// hhPhysics_Delta::Evaluate +//============ +bool hhPhysics_Delta::Evaluate( int timeStepMSec, int endTimeMSec ) { + trace_t trace; + idVec3 dest; + idRotation rotation; + float timeStep; + idVec3 velocity; + + + timeStep = MS2SEC( timeStepMSec ); + velocity = delta / timeStep; + + if ( delta == vec3_zero ) { + Rest(); + return( false ); + } + + clipModel->Unlink(); // Taken from monster + + dest = GetOrigin() + delta; + rotation.SetOrigin( GetOrigin() ); + rotation.SetAngle( 0 ); + + //? Maybe model after the idPhysics_Monster::Slide move, where we + // slide along scholng + + // If there was a collision, adjust the dest + if ( gameLocal.clip.Motion( trace, GetOrigin(), dest, rotation, clipModel, + GetAxis(), clipMask, self ) ) { + if ( self->AllowCollision( trace ) ) { + //gameLocal.Printf( "Hit something %.2f\n", trace.fraction ); + + dest = trace.endpos; + + //? Move this elsewhere? + self->Collide( trace, velocity ); + } //. We run into what we hit = ) + + //? Maybe we apply an impulse? + } //. We hit something + + //gameLocal.Printf( "Moving from %s to %s (%s)\n", GetOrigin().ToString(), + // dest.ToString(), delta.ToString() ); + + + //! What about origin and axis? + clipModel->Link( gameLocal.clip, self, 0, dest, GetAxis() ); // Taken from monster + + // We are done with delta, so zero it out + delta.Zero(); + + return( true ); +} //. hhPhysics_Delta::Evaluate( int, int ); + + +//============ +// hhPhysics_Delta::SetDelta +//============ +void hhPhysics_Delta::SetDelta( const idVec3 &d ){ + + delta = d; + + if ( delta != vec3_origin ) { + Activate(); + } +} //. hhPhysics_Delta::SetDelta( const idVec3 & ) + + +//============ +// hhPhysics_Delta::Activate +//============ +void hhPhysics_Delta::Activate(){ + + self->BecomeActive( TH_PHYSICS ); + +} //. hhPhysics_Delta::Activate( const idVec3 & ) + + +//============ +// hhPhysics_Delta::Rest +//============ +void hhPhysics_Delta::Rest(){ + + self->BecomeInactive( TH_PHYSICS ); + +} //. hhPhysics_Delta::Rest( const idVec3 & ) + + + +//================ +//hhPhysics_Delta::SetAxis +//================ +void hhPhysics_Delta::SetAxis( const idMat3 &newAxis, int id ) { + // Ripped from idPhysics_Monster + clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis ); + //? Activate(); +} + + +//================ +//hhPhysics_Delta::SetOrigin +//================ +void hhPhysics_Delta::SetOrigin( const idVec3 &newOrigin, int id ) { + // Ripped from idPhysics_Monster + /* + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } + */ + clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() ); + //? Activate(); +} + +//================ +//hhPhysics_Delta::Save +//================ +void hhPhysics_Delta::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( delta ); +} + +//================ +//hhPhysics_Delta::Restore +//================ +void hhPhysics_Delta::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( delta ); +} + diff --git a/neo/prey/Prey/physics_delta.h b/neo/prey/Prey/physics_delta.h new file mode 100644 index 00000000..f709e1b1 --- /dev/null +++ b/neo/prey/Prey/physics_delta.h @@ -0,0 +1,34 @@ +#ifndef __PREY_PHYSICS_DELTA_H__ +#define __PREY_PHYSICS_DELTA_H__ + + + +class hhPhysics_Delta : public idPhysics_Actor { + + public: + CLASS_PROTOTYPE( hhPhysics_Delta ); + + hhPhysics_Delta(); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + + + void SetDelta( const idVec3 &d ); + + void Activate(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + void Rest(); + + idVec3 delta; + +}; + +#endif /* __PREY_PHYSICS_DELTA_H__ */ diff --git a/neo/prey/Prey/physics_preyai.cpp b/neo/prey/Prey/physics_preyai.cpp new file mode 100644 index 00000000..017e728e --- /dev/null +++ b/neo/prey/Prey/physics_preyai.cpp @@ -0,0 +1,573 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// HUMANHEAD nla - Class constants +const int hhPhysics_AI::NO_FLY_DIRECTION = 1000; + +CLASS_DECLARATION( idPhysics_Monster, hhPhysics_AI ) +END_CLASS + +/* +===================== +hhPhysics_AI::hhPhysics_AI +===================== +*/ +hhPhysics_AI::hhPhysics_AI() { + // HUMANHEAD nla + flyStepDirection = NO_FLY_DIRECTION; + //HUMANHEAD END + lastMoveTouch = NULL; + useGravity = TRUE; + bGravClipModelAxis = false; +} + +/* +===================== +hhPhysics_AI::SlideMove +===================== +// HUMANHEAD nla - if touched is NULL, the touched entities will be added +// directly. Otherwise they will be added to the list +*/ +monsterMoveResult_t hhPhysics_AI::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta, idList *touched ) { + trace_t tr; + idVec3 move; + int i; + + blockingEntity = NULL; + move = delta; + for( i = 0; i < 3; i++ ) { + gameLocal.clip.Translation( tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( tr.c.entityNum != ENTITYNUM_WORLD && tr.c.entityNum != ENTITYNUM_NONE && gameLocal.entities[ tr.c.entityNum ] ) + lastMoveTouch = gameLocal.entities[ tr.c.entityNum ]; + + + 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 ); + */ + + // HUMANHEAD nla - Added logic to allow monsters to push + // if we can push other entities and not blocked by the world. Added PushCosine + if ( self->Pushes() && ( tr.c.entityNum != ENTITYNUM_WORLD ) ) { + // Early out if we aren't facing enough towards the object + idVec3 normVelocity( velocity ); + + normVelocity.NormalizeFast(); + /* + gameLocal.Printf( "%d Trying %s * %s = %.2f vs %.2f\n", gameLocal.time, + normVelocity.ToString(), tr.c.normal.ToString(), + idMath::Fabs( normVelocity * tr.c.normal ), self->PushCosine() ); + */ + if ( idMath::Fabs( normVelocity * tr.c.normal ) >= self->PushCosine() ) { + trace_t trPush; + int pushFlags; + float totalMass; + + clipModel->SetPosition( start, 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; + + // clip & push + totalMass = gameLocal.push.ClipTranslationalPush( trPush, self, pushFlags, start + move, move ); + + if ( totalMass > 0.0f ) { + // decrease velocity based on the total mass of the objects being pushed ? + /*? Put back in later? + if ( velocity.LengthSqr() > Square( crouchSpeed * 0.8f ) ) { + velocity *= crouchSpeed * 0.8f / velocity.Length(); + } + pushed = true; + */ + } + + // Added logic to prevent pushing + tr = trPush; + start = tr.endpos; + // time_left -= time_left * trace.fraction; + + // if moved the entire distance + // Changed from orig. + /* + if ( trace.fraction >= 1.0f ) { + break; + } + */ + // Changed to this! = ) + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return MM_SLIDING; + } + return MM_OK; + } + } + } + // HUMANHEAD END + + // clip the movement delta and velocity + move.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + } + +/* NOTE: Underlying code changed some, fit this back in if needed. + //HUMANHEAD nla + if (touched == NULL) { + if (tr.fraction < 1.0f) { + AddTouchEnt(tr.c.entityNum); + } + } + else { + touched->Append(tr.c.entityNum); + } +*/ + return MM_BLOCKED; +} + +// HUMANHEAD nla +/* +===================== +hhPhysics_AI::FlyMove + + move start into the delta direction + the velocity is clipped conform any collisions +===================== +*/ +#define NUM_DIRECTIONS 2 +monsterMoveResult_t hhPhysics_AI::FlyMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + trace_t tr; + idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel, dirGrav; + monsterMoveResult_t result1, result2; + idVec3 originalStart, originalVelocity, bestStart, bestVelocity; + float noStepDistSq, distSq, bestDistSq; + int bestFlyStepDirection; + monsterMoveResult_t bestReturn; + idList noStepEntities, stepEntities, bestEntities; + + + if ( delta == vec3_origin ) { + return MM_OK; + } + + // Initialize + originalStart = start; + originalVelocity = velocity; + bestFlyStepDirection = NO_FLY_DIRECTION; + + // try to move without stepping up + noStepPos = start; + noStepVel = velocity; + result1 = SlideMove( noStepPos, noStepVel, delta, &noStepEntities ); + if ( result1 == MM_OK ) { + AddTouchEntList( noStepEntities ); + start = noStepPos; + velocity = noStepVel; + return MM_OK; + } + + // Assume no stepping is the best + bestStart = noStepPos; + bestVelocity = noStepVel; + bestReturn = result1; + bestEntities = noStepEntities; + // Add small fudge factor to take into account float issues + noStepDistSq = ((noStepPos - originalStart) * 1.001f).LengthSqr(); + bestDistSq = noStepDistSq; + + // Try to move around the obstacle + for ( int direction = 0; direction < NUM_DIRECTIONS ; direction++ ) { + stepEntities.Clear(); + + //! Maybe make this relative to the clip model? + if (direction == 0) { // Set the direction. Assumes 2x + dirGrav.Set(0, 0, -1); + } + else { + dirGrav *= -1; + } + + // try to step "up" + up = start - dirGrav * maxStepHeight; + gameLocal.clip.Translation( tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( tr.fraction == 0.0f ) { + start = originalStart; // Reset + velocity = originalVelocity; + continue; + } + + // Add the entity touched + if ( tr.fraction < 1.0f ) { + stepEntities.Append( tr.c.entityNum ); + } + + // try to move at the stepped up position + stepPos = tr.endpos; + stepVel = velocity; + result2 = SlideMove( stepPos, stepVel, delta, &stepEntities ); + if ( result2 == MM_BLOCKED ) { // Couldn't move all the way at stepped pos + start = originalStart; // Reset + velocity = originalVelocity; + distSq = (stepPos - tr.endpos).LengthSqr(); // See if we moved further up there. Ignore the step up + if ((distSq > bestDistSq)) { + bestStart = stepPos; + bestVelocity = stepVel; + bestReturn = result2; + bestEntities = stepEntities; + bestDistSq = distSq; + bestFlyStepDirection = direction; + } + continue; + } + + // Stepping is the best move + start = originalStart; // Reset + velocity = originalVelocity; + distSq = ( stepPos - originalStart ).LengthSqr(); // Include the distance up we traveled + if ( ( direction == flyStepDirection ) || // Going the same direction as last time + ( ( ( direction < flyStepDirection ) || // Haven't tested the last direction + ( bestFlyStepDirection != flyStepDirection ) ) && // Have tested the last, but coundn't go that way + ( distSq > bestDistSq ) ) // Closer than the best + ) { + bestStart = stepPos; // Save the no step version + bestVelocity = stepVel; + bestReturn = MM_STEPPED; + bestEntities = stepEntities; + bestDistSq = distSq; + bestFlyStepDirection = direction; + } + + } //. Direction loop + + // Didn't work, use the no step return + start = bestStart; + velocity = bestVelocity; + flyStepDirection = bestFlyStepDirection; + AddTouchEntList( bestEntities ); + + return( bestReturn ); +} + +/* +================ +hhPhysics_AI::Evaluate +================ +*/ +bool hhPhysics_AI::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + float timeStep; + float oldMasterYaw, oldMasterDeltaYaw; // HUMANHEAD pdm + + timeStep = MS2SEC( timeStepMSec ); + + moveResult = MM_OK; + blockingEntity = NULL; + oldOrigin = current.origin; + oldMasterYaw = masterYaw; // HUMANHEAD pdm + oldMasterDeltaYaw = masterDeltaYaw; // HUMANHEAD pdm + + //HUMANHEAD: aob + HadGroundContacts( HasGroundContacts() ); + //HUMANHEAD END + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + //HUMANHEAD rww + if (!timeStep) { + current.velocity = vec3_origin; + } + else { + //HUMANHEAD END + current.velocity = ( current.origin - oldOrigin ) / timeStep; + } + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + + // HUMANHEAD pdm: If we haven't moved and master is at rest, put me to rest + if (current.origin == oldOrigin && masterYaw == oldMasterYaw && masterDeltaYaw == oldMasterDeltaYaw) { + if (masterEntity->IsAtRest()) { + Rest(); + } + return false; + } + // HUMANHEAD END + return true; + } + + // if the monster is at rest + if ( current.atRest >= 0 ) { + return false; + } + + assert(timeStep != 0.0f); //HUMANHEAD rww + + ActivateContactEntities(); + + // move the monster velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + clipModel->Unlink(); + + // check if on the ground + //HUMANHEAD: aob - moved ground check to after movement + + // 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 || self->fl.isTractored || ( !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 ) { + //HUMANHEAD: aob - removed scope hardcode + moveResult = SlideMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + + if ( !fly && !self->fl.isTractored && IsGravityEnabled()) { // HUMANHEAD JRM: Gravity toggle + 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 + //HUMANHEAD: aob + current.velocity = delta / timeStep; + //HUMANHEAD END + + //HUMANHEAD: aob - removed scope hardcode + //moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta ); + if( IsGravityEnabled() ) { + moveResult = StepMove( current.origin, current.velocity, delta ); + } else { + moveResult = SlideMove( current.origin, current.velocity, delta ); + } + + delta.Zero(); + } + } + + //HUMANHEAD: aob - for changing gravity zones + idVec3 rotationCheckOrigin = GetOrigin() + GetAxis()[2] * GetBounds()[1].z; + IterativeRotateMove( GetAxis()[2], -GetGravityNormal(), GetOrigin(), rotationCheckOrigin, p_iterRotMoveNumIterations.GetInteger() ); + //HUMANHEAD END + + clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); + + // check if on the ground + //HUMANHEAD: aob - removed scope hardcode + CheckGround( current ); + + // get all the ground contacts + EvaluateContacts(); + + // move the monster velocity back into the world frame + current.velocity += current.pushVelocity; + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + // HUMANHEAD pdm: Allow some things to go outside world without warning + if (!self->IsType(hhWraith::Type) && !self->IsType( hhTalon::Type ) ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + } + Rest(); + } + + return ( current.origin != oldOrigin ); +} + +/* +================== +hhPhysics_AI::ApplyFriction + +Handles both ground friction and water friction + +//HUMANHEAD: aob +================== +*/ +idVec3 hhPhysics_AI::ApplyFriction( const idVec3& vel, const float deltaTime ) { + float speed = 0.0f; + float newSpeed = 0.0f; + float control = 0.0f; + float drop = 0.0f; + idVec3 velocity = vel; + + speed = velocity.Length(); + if( speed <= VECTOR_EPSILON ) { + return vec3_origin; + } + + if( fly || self->fl.isTractored ) { + drop += speed * PM_FLYFRICTION * deltaTime; + } + else if( !current.onGround ) { + drop += speed * PM_AIRFRICTION * deltaTime; + } + else if( current.onGround ) { + if( !(GetGroundSurfaceFlags() & SURF_SLICK) ) { + drop += speed * PM_FRICTION * deltaTime; + } + } + + // scale the velocity + newSpeed = speed - drop; + if( newSpeed < 0.0f ) { + newSpeed = 0.0f; + } + + return vel * ( newSpeed / speed ); +} + +/* +================ +hhPhysics_AI::AddForce + HUMANHEAD pdm: added so that springs will work on monsters +================ +*/ +void hhPhysics_AI::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + // HUMANHEAD pdm: so we can use forces on them + current.velocity += (force / GetMass()) * USERCMD_ONE_OVER_HZ; + + Activate(); +} + +/* +================ +hhPhysics_AI::SetLinearVelocity +================ +*/ +void hhPhysics_AI::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; + + Activate(); +} + +/* +================ +hhPhysics_AI::GetLinearVelocity +================ +*/ +const idVec3& hhPhysics_AI::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +hhPhysics_AI::ApplyImpulse +================ +*/ +void hhPhysics_AI::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + // HUMANHEAD: aob - so we can use forces on them (crane) + current.velocity += impulse / GetMass(); + // HUMANHEAD END + + Activate(); +} + +/* +================ +hhPhysics_AI::SetMaster +overridden to set localAxis +================ +*/ +void hhPhysics_AI::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(); + idAI *entAI = static_cast(self); + if ( entAI ) { + idAngles angles( 0, entAI->spawnArgs.GetFloat( "angle" ), 0 ); + localAxis = angles.ToMat3() * masterAxis.Transpose(); + } + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + Activate(); + } + } +} + +/* +================ +hhPhysics_AI::SetGravity +overridden to call SetClipModelAxis for asteroid gravity +================ +*/ +void hhPhysics_AI::SetGravity( const idVec3 &newGravity ) { + idPhysics_Monster::SetGravity( newGravity ); + if ( bGravClipModelAxis ) { + SetClipModelAxis(); + } +} + +//================ +//hhPhysics_AI::Save +//================ +void hhPhysics_AI::Save( idSaveGame *savefile ) const { + savefile->WriteInt( flyStepDirection ); + lastMoveTouch.Save( savefile ); + savefile->WriteBool( useGravity ); + savefile->WriteMat3( localAxis ); + savefile->WriteBool( bGravClipModelAxis ); +} + +//================ +//hhPhysics_AI::Restore +//================ +void hhPhysics_AI::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( flyStepDirection ); + lastMoveTouch.Restore( savefile ); + savefile->ReadBool( useGravity ); + savefile->ReadMat3( localAxis ); + savefile->ReadBool( bGravClipModelAxis ); +} + diff --git a/neo/prey/Prey/physics_preyai.h b/neo/prey/Prey/physics_preyai.h new file mode 100644 index 00000000..ca525e79 --- /dev/null +++ b/neo/prey/Prey/physics_preyai.h @@ -0,0 +1,45 @@ +#ifndef __HH_PHYSICS_AI +#define __HH_PHYSICS_AI + +class hhPhysics_AI : public idPhysics_Monster { + CLASS_PROTOTYPE( hhPhysics_AI ); + + public: + hhPhysics_AI(); + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + const idVec3& GetLinearVelocity( int id = 0 ) const; + virtual void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + idVec3 GetLocalOrigin( void ) { return current.localOrigin; } + + idEntity* GetLastMoveTouch(void) {return lastMoveTouch.GetEntity();} + + void EnableGravity(bool tf = true) {useGravity = tf;} + bool IsGravityEnabled(void) const {return useGravity;} + void SetMaster( idEntity *master, const bool orientated ); + void SetGravity( const idVec3 &newGravity ); + void SetGravClipModelAxis( bool enable ) { bGravClipModelAxis = enable; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + idVec3 ApplyFriction( const idVec3& vel, const float deltaTime ); + virtual monsterMoveResult_t FlyMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + virtual monsterMoveResult_t SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta, idList *touched = NULL ); + + // HUMANHEAD nla - Constants + public: + static const int NO_FLY_DIRECTION; + idMat3 localAxis; + + protected: + int flyStepDirection; // Direction we were last stepping in + idEntityPtr lastMoveTouch; // Entity we touched on last slidemove + bool useGravity; + bool bGravClipModelAxis; // Set clipmodel axis for gravity changes +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/physics_preyparametric.cpp b/neo/prey/Prey/physics_preyparametric.cpp new file mode 100644 index 00000000..b2c4f77f --- /dev/null +++ b/neo/prey/Prey/physics_preyparametric.cpp @@ -0,0 +1,302 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idPhysics_Static, hhPhysics_StaticWeapon ) +END_CLASS + +/* +============ +hhPhysics_StaticWeapon::hhPhysics_StaticWeapon +============ +*/ +hhPhysics_StaticWeapon::hhPhysics_StaticWeapon() { + castSelf = NULL; + selfOwner = NULL; +} + +/* +============ +hhPhysics_StaticWeapon::SetSelfOwner +============ +*/ +void hhPhysics_StaticWeapon::SetSelfOwner( idActor* a ) { + if( self && self->IsType(hhWeapon::Type) ) { + castSelf = static_cast( self ); + } + + if( a && a->IsType(hhPlayer::Type) ) { + selfOwner = static_cast( a ); + } +} + +/* +============ +hhPhysics_StaticWeapon::SetLocalAxis +============ +*/ +void hhPhysics_StaticWeapon::SetLocalAxis( const idMat3& newLocalAxis ) { + current.localAxis = newLocalAxis; +} + +/* +============ +hhPhysics_StaticWeapon::SetLocalOrigin +============ +*/ +void hhPhysics_StaticWeapon::SetLocalOrigin( const idVec3& newLocalOrigin ) { + current.localOrigin = newLocalOrigin; +} + +/* +============ +hhPhysics_StaticWeapon::Evaluate +============ +*/ +bool hhPhysics_StaticWeapon::Evaluate( int timeStepMSec, int endTimeMSec ) { + if( !selfOwner ) { + return false; + } + + idMat3 localAxis; + idVec3 localOrigin( 0.0f, 0.0f, selfOwner->EyeHeight() ); + idAngles pitchAngles( selfOwner->GetUntransformedViewAngles().pitch, 0.0f, 0.0f ); + + if( selfOwner->InVehicle() ) { + localAxis = pitchAngles.ToMat3(); + } else { + localAxis = ( pitchAngles + selfOwner->GunTurningOffset() ).ToMat3(); + localOrigin += selfOwner->GunAcceleratingOffset(); + if ( castSelf ) { + castSelf->MuzzleRise( localOrigin, localAxis ); + } + } + + SetLocalAxis( localAxis ); + SetLocalOrigin( localOrigin ); + + return idPhysics_Static::Evaluate( timeStepMSec, endTimeMSec ); +} + +//================ +//hhPhysics_StaticWeapon::Save +//================ +void hhPhysics_StaticWeapon::Save( idSaveGame *savefile ) const { + savefile->WriteObject( castSelf ); + savefile->WriteObject( selfOwner ); +} + +//================ +//hhPhysics_StaticWeapon::Restore +//================ +void hhPhysics_StaticWeapon::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( castSelf ) ); + savefile->ReadObject( reinterpret_cast( selfOwner ) ); +} + +CLASS_DECLARATION( idPhysics_Static, hhPhysics_StaticForceField ) +END_CLASS + +/* +================ +hhPhysics_StaticForceField::hhPhysics_StaticForceField +================ +*/ +hhPhysics_StaticForceField::hhPhysics_StaticForceField() { +} + +/* +================ +hhPhysics_StaticForceField::Evaluate +================ +*/ +bool hhPhysics_StaticForceField::Evaluate( int timeStepMSec, int endTimeMSec ) { + bool moved = idPhysics_Static::Evaluate( timeStepMSec, endTimeMSec ); + + EvaluateContacts(); + + return moved; +} + +/* +================ +hhPhysics_StaticForceField::AddContactEntitiesForContacts +================ +*/ +void hhPhysics_StaticForceField::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 ); + } + } +} + +/* +================ +hhPhysics_StaticForceField::ActivateContactEntities +================ +*/ +void hhPhysics_StaticForceField::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-- ); + } + } +} + +/* +================ +hhPhysics_StaticForceField::EvaluateContacts +================ +*/ +bool hhPhysics_StaticForceField::EvaluateContacts( void ) { + idVec6 dir; + int num; + + ClearContacts(); + + contacts.SetNum( 10, false ); + + dir.SubVec3(0).Zero(); + dir.SubVec3(1).Zero(); + //dir.SubVec3(0).Normalize(); + //dir.SubVec3(1).Normalize(); + num = gameLocal.clip.Contacts( &contacts[0], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), MASK_SOLID, self ); + contacts.SetNum( num, false ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +hhPhysics_StaticForceField::GetNumContacts +================ +*/ +int hhPhysics_StaticForceField::GetNumContacts( void ) const { + return contacts.Num(); +} + +/* +================ +hhPhysics_StaticForceField::GetContact +================ +*/ +const contactInfo_t &hhPhysics_StaticForceField::GetContact( int num ) const { + return contacts[num]; +} + +/* +================ +hhPhysics_StaticForceField::ClearContacts +================ +*/ +void hhPhysics_StaticForceField::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 ); +} + +/* +================ +hhPhysics_StaticForceField::AddContactEntity +================ +*/ +void hhPhysics_StaticForceField::AddContactEntity( 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 ) { + return; + } + } + contactEntities.Alloc() = e; +} + +/* +================ +hhPhysics_StaticForceField::RemoveContactEntity +================ +*/ +void hhPhysics_StaticForceField::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; + } + } +} + +//================ +//hhPhysics_StaticForceField::Save +//================ +void hhPhysics_StaticForceField::Save( idSaveGame *savefile ) const { + int i; + 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++ ) { + //HUMANHEAD PCF mdl 04/26/06 - Changed '->' to a '.', as it should have been + contactEntities[i].Save( savefile ); + } +} + +//================ +//hhPhysics_StaticForceField::Restore +//================ +void hhPhysics_StaticForceField::Restore( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt( num ); + contacts.SetNum( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadContactInfo( contacts[i] ); + } + + savefile->ReadInt( num ); + contactEntities.SetNum( num ); + for ( i = 0; i < num; i++ ) { + //HUMANHEAD PCF mdl 04/26/06 - Changed '->' to a '.', as it should have been + contactEntities[i].Restore( savefile ); + } +} + diff --git a/neo/prey/Prey/physics_preyparametric.h b/neo/prey/Prey/physics_preyparametric.h new file mode 100644 index 00000000..95f78eec --- /dev/null +++ b/neo/prey/Prey/physics_preyparametric.h @@ -0,0 +1,54 @@ + +#ifndef __PREY_PHYSICS_PARAMETRIC_H__ +#define __PREY_PHYSICS_PARAMETRIC_H__ + +class hhWeapon; +class hhPlayer; + +class hhPhysics_StaticWeapon : public idPhysics_Static { + CLASS_PROTOTYPE( hhPhysics_StaticWeapon ); + + public: + hhPhysics_StaticWeapon(); + + virtual void SetSelfOwner( idActor* a ); + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ); + + void SetLocalAxis( const idMat3& newLocalAxis ); + void SetLocalOrigin( const idVec3& newLocalOrigin ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + hhWeapon* castSelf; + hhPlayer* selfOwner; +}; + +class hhPhysics_StaticForceField : public idPhysics_Static { + CLASS_PROTOTYPE( hhPhysics_StaticForceField ); + + public: + hhPhysics_StaticForceField(); + + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ); + + virtual bool EvaluateContacts( void ); + virtual int GetNumContacts( void ) const; + virtual const contactInfo_t & GetContact( int num ) const; + virtual void ClearContacts( void ); + virtual void AddContactEntitiesForContacts( void ); + virtual void AddContactEntity( idEntity *e ); + virtual void RemoveContactEntity( idEntity *e ); + + virtual void ActivateContactEntities( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + idList contacts; // contacts + idList contactEntities; +}; + +#endif /* __PREY_PHYSICS_PARAMETRIC_H__ */ diff --git a/neo/prey/Prey/physics_simple.cpp b/neo/prey/Prey/physics_simple.cpp new file mode 100644 index 00000000..66eaf813 --- /dev/null +++ b/neo/prey/Prey/physics_simple.cpp @@ -0,0 +1,50 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idPhysics_RigidBody, hhPhysics_RigidBodySimple ) +END_CLASS + +/* +================ +SimpleRigidBodyDerivatives +================ +*/ +void SimpleRigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ) { + const hhPhysics_RigidBodySimple *p = (hhPhysics_RigidBodySimple *) 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; + + // derivatives + d->linearVelocity = p->inverseMass * s->linearMomentum; + d->angularMatrix.Zero(); + //d->angularMatrix = SkewSymmetric( vec3_zero ) * s->orientation; + d->force = - p->linearFriction * s->linearMomentum + p->current.externalForce; + d->torque.Zero(); +} + +/* +================ +hhPhysics_RigidBodySimple::hhPhysics_RigidBodySimple +================ +*/ +hhPhysics_RigidBodySimple::hhPhysics_RigidBodySimple() { + SAFE_DELETE_PTR( integrator ); + integrator = new idODE_Euler( sizeof(rigidBodyIState_t) / sizeof(float), SimpleRigidBodyDerivatives, this ); +} + +/* +================ +hhPhysics_RigidBodySimple::Integrate +================ +*/ +void hhPhysics_RigidBodySimple::Integrate( const float deltaTime, rigidBodyPState_t &next ) { + idPhysics_RigidBody::Integrate( deltaTime, next ); +} \ No newline at end of file diff --git a/neo/prey/Prey/physics_simple.h b/neo/prey/Prey/physics_simple.h new file mode 100644 index 00000000..e4ea5d99 --- /dev/null +++ b/neo/prey/Prey/physics_simple.h @@ -0,0 +1,15 @@ +#ifndef __HH_PHYSICS_SIMPLE_H__ +#define __HH_PHYSICS_SIMPLE_H__ + +class hhPhysics_RigidBodySimple : public idPhysics_RigidBody { + CLASS_PROTOTYPE( hhPhysics_RigidBodySimple ); + public: + hhPhysics_RigidBodySimple(); + + virtual void Integrate( const float deltaTime, rigidBodyPState_t &next ); + + protected: + friend void SimpleRigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/physics_vehicle.cpp b/neo/prey/Prey/physics_vehicle.cpp new file mode 100644 index 00000000..05d7d520 --- /dev/null +++ b/neo/prey/Prey/physics_vehicle.cpp @@ -0,0 +1,234 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +//#define DEBUG_VEHICLE_PHYSICS 1 +#define VEHICLE_DEBUG if(g_vehicleDebug.GetInteger()) gameLocal.Printf +#if DEBUG_VEHICLE_PHYSICS + #define CheckSolid(a, b, c, d, e, f) DoCheckSolid(a, b, c, d, e, f) +#else + #define CheckSolid(a, b, c, d, e, f) 0 +#endif + +//------------------------------------------------------------- +// +// CheckSolid: utility function for checking for collision errors +// +//------------------------------------------------------------- +bool DoCheckSolid(const char *text, idVec3 &pos, idClipModel *clipModel, int clipMask, idMat3 &axis, idEntity *pass) { + int myContents = gameLocal.clip.Contents(pos, clipModel, axis, clipMask, pass); + if( myContents ) { + VEHICLE_DEBUG("%s\n", text); + VEHICLE_DEBUG(" position: %s\n", pos.ToString()); + VEHICLE_DEBUG(" orientation: %s\n", axis.ToAngles().ToString()); + VEHICLE_DEBUG(" contents: %d\n", myContents); + return true; + } + return false; +} + + +CLASS_DECLARATION( hhPhysics_RigidBodySimple, hhPhysics_Vehicle ) +END_CLASS + +//------------------------------------------------------------- +// +// hhPhysics_Vehicle::VehicleMotion +// +//------------------------------------------------------------- +bool hhPhysics_Vehicle::VehicleMotion(trace_t &collision, const idVec3 &start, const idVec3 &end, const idMat3 &axis) { + trace_t translationalTrace; + idVec3 move = end - start; + idVec3 curPosition = start; + idMat3 curAxis = axis; + + memset(&collision, 0, sizeof(collision)); // sanity + + collision.fraction = 1.0f; // Assume success until proven otherwise + + // + // try to slide move + // + for(int i = 0; i < 3; i++ ) { + + gameLocal.clip.Translation( translationalTrace, curPosition, curPosition + move, clipModel, curAxis, clipMask, self ); + curPosition = translationalTrace.endpos; + + CheckSolid(va("SlideMoved (%d) into something solid during slidemove!", i), curPosition, clipModel, clipMask, curAxis, self); + + // Keep the shortest frac + if (translationalTrace.fraction < collision.fraction) { + collision.fraction = translationalTrace.fraction; + collision.c = translationalTrace.c; + } + + if ( translationalTrace.fraction == 1.0f ) { + break; + } + + // Shorten the move by the amount moved + move *= (1.0f - translationalTrace.fraction); + + // project movement and momentum onto the sliding surface + move.ProjectOntoPlane( translationalTrace.c.normal, OVERCLIP ); + } + + collision.endAxis = curAxis; + collision.endpos = curPosition; + + return collision.fraction < 1.0f; +} + +//------------------------------------------------------------- +// +// hhPhysics_Vehicle::CheckForCollisions +// +// Evaluate the impulse based rigid body physics. +// in the event of a collision, tries to do a slide move +//------------------------------------------------------------- +bool hhPhysics_Vehicle::CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ) { + bool collided = false; + + CheckSolid("Before VehicleMotion: in something solid!", current.i.position, clipModel, clipMask, current.i.orientation, self); + + collided = VehicleMotion( collision, current.i.position, next.i.position, current.i.orientation ); + if( collided ) { + // set the next state to the state at the moment of impact + next.i.position = collision.endpos; + next.i.orientation = collision.endAxis; + collided = true; + } + + CheckSolid("After VehicleMotion: in something solid!", next.i.position, clipModel, clipMask, next.i.orientation, self); + + return collided; +} + +/* +================ +hhPhysics_Vehicle::SetFriction +================ +*/ +void hhPhysics_Vehicle::SetFriction( const float linear, const float angular, const float contact ) { + //don't cap the friction for the vehicle + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +const float VEH_VELOCITY_MAX = 16000; +const int VEH_VELOCITY_TOTAL_BITS = 16; +const int VEH_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( VEH_VELOCITY_MAX ) ) + 1; +const int VEH_VELOCITY_MANTISSA_BITS = VEH_VELOCITY_TOTAL_BITS - 1 - VEH_VELOCITY_EXPONENT_BITS; +const float VEH_MOMENTUM_MAX = 1e20f; +const int VEH_MOMENTUM_TOTAL_BITS = 16; +const int VEH_MOMENTUM_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( VEH_MOMENTUM_MAX ) ) + 1; +const int VEH_MOMENTUM_MANTISSA_BITS = VEH_MOMENTUM_TOTAL_BITS - 1 - VEH_MOMENTUM_EXPONENT_BITS; +const float VEH_FORCE_MAX = 1e20f; +const int VEH_FORCE_TOTAL_BITS = 16; +const int VEH_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( VEH_FORCE_MAX ) ) + 1; +const int VEH_FORCE_MANTISSA_BITS = VEH_FORCE_TOTAL_BITS - 1 - VEH_FORCE_EXPONENT_BITS; + +/* +================ +hhPhysics_Vehicle::WriteToSnapshot +================ +*/ +void hhPhysics_Vehicle::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.i.orientation.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteFloat( gravityVector.x ); + msg.WriteFloat( gravityVector.y ); + msg.WriteFloat( gravityVector.z ); + + msg.WriteBits(dropToFloor, 1); + + 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], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[1], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[2], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[0], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[1], VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[2], VEH_MOMENTUM_EXPONENT_BITS, VEH_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], VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[0], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[1], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[2], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[0], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[1], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[2], VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); +} + +/* +================ +hhPhysics_Vehicle::ReadFromSnapshot +================ +*/ +void hhPhysics_Vehicle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + idVec3 newGrav; + newGrav.x = msg.ReadFloat(); + newGrav.y = msg.ReadFloat(); + newGrav.z = msg.ReadFloat(); + + SetGravity(newGrav); + + dropToFloor = !!msg.ReadBits(1); + + 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( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[1] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[2] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[0] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[1] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[2] = msg.ReadFloat( VEH_MOMENTUM_EXPONENT_BITS, VEH_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, VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, VEH_VELOCITY_EXPONENT_BITS, VEH_VELOCITY_MANTISSA_BITS ); + current.externalForce[0] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalForce[1] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalForce[2] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalTorque[0] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalTorque[1] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + current.externalTorque[2] = msg.ReadDeltaFloat( 0.0f, VEH_FORCE_EXPONENT_BITS, VEH_FORCE_MANTISSA_BITS ); + + current.i.orientation = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); + + if ( clipModel ) { + clipModel->Link( gameLocal.clip, self, clipModel->GetId(), current.i.position, current.i.orientation ); + } +} diff --git a/neo/prey/Prey/physics_vehicle.h b/neo/prey/Prey/physics_vehicle.h new file mode 100644 index 00000000..a3316948 --- /dev/null +++ b/neo/prey/Prey/physics_vehicle.h @@ -0,0 +1,37 @@ + +#ifndef __HH_PHYSICS_VEHICLE_H__ +#define __HH_PHYSICS_VEHICLE_H__ + +/* +=================================================================================== + + hhPhysics_Vehicle + + Simulates the motion of a vehicle through the environment. The orientation of + the vehicle is controlled directly by the player, so the physics move the + vehicle so that it will fit at it's current orientation. + +=================================================================================== +*/ + +class hhPhysics_Vehicle : public hhPhysics_RigidBodySimple { + +public: + CLASS_PROTOTYPE( hhPhysics_Vehicle ); + + virtual void SetFriction( const float linear, const float angular, const float contact ); + + const idVec3& GetLinearMomentum() const { return current.i.linearMomentum; } + const idVec3& GetAngularMomentum() const { return current.i.angularMomentum; } + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + virtual bool CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ); + + bool VehicleMotion( trace_t &collision, const idVec3 &start, const idVec3 &end, const idMat3 &axis ); +}; + + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_animator.cpp b/neo/prey/Prey/prey_animator.cpp new file mode 100644 index 00000000..4227685a --- /dev/null +++ b/neo/prey/Prey/prey_animator.cpp @@ -0,0 +1,607 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +/* +============ +hhAnimator::hhAnimator +============ +*/ + +hhAnimator::hhAnimator() { + + lastCycleRotate = 0; + +} //. hhAnimator::hhAnimator() + + +/* +===================== +hhAnimator::CycleAnimRandom +===================== +*/ +void hhAnimator::CycleAnim( int channelNum, int anim, int currentTime, int blendTime, const idEventDef *pEvent ) { + + //! NLANOTE - Add ability to skip cycling + idAnimator::CycleAnim( channelNum, anim, currentTime, blendTime ); + + if ( !anim ) { return; } + + + hhAnim *animPtr = (hhAnim *)GetAnim( anim ); + //HUMANHEAD rww - crash here, can't repro. putting extra logic in for non-gold builds to check. +#if !GOLD + if (!animPtr) { + assert(0); + common->Warning("hhAnimator::CycleAnim anim index %i returned NULL for GetAnim.", anim); + return; + } +#endif +//HUMANHEAD END + + // If the anim was an exact match to the requested name, if so, just return + if ( animPtr->exactMatch ) { + return; + } + + // Determine if there are multiple anims to play. If not, then just return. + int numAnimVariants = GetNumAnimVariants( anim ); + + if ( numAnimVariants < 2 ) { + return; + } + + // The CycleAnim could have started at a random spot in the anim, take this into account, and add only the time left in the anim + int lengthLeft; + lengthLeft = animPtr->Length() - channels[ channelNum ][0].AnimTime( currentTime ); + + channels[ channelNum ][0].rotateTime = currentTime + lengthLeft; + channels[ channelNum ][0].rotateEvent = pEvent; + + entity->PostEventMS( &EV_CheckCycleRotate, lengthLeft ); + +} //. BlendCycleRandom( int, idAnim *, float, int, int ) + + +/* +============== +hhAnimator::GetNumAnimVariants +============== +*/ +int hhAnimator::GetNumAnimVariants( int anim ) { + const char *name = GetAnim( anim )->Name(); + int len, numAnims, maxAnims; + + + // nla - Copied from idDeclModelDef::GetAnim( const char * ) + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return( 1 ); + } + + // find all animations with same name + numAnims = 0; + maxAnims = NumAnims(); + + for( int i = 1; i < maxAnims; i++ ) { + if ( !strcmp( GetAnim( i )->Name(), name ) ) { + numAnims++; + } + } + + return( numAnims ); +} //. hhAnimator::GetNumAnimVariants( int ) + + + +/* +===================== +hhAnimator::CheckCycleRotate + Check the blend animations to see if we need to switch any animations +===================== +*/ +void hhAnimator::CheckCycleRotate() { + hhAnimBlend *blend; + int anim; + int i; + const idEventDef *event; + + + if ( gameLocal.time <= lastCycleRotate ) { + return; + } + lastCycleRotate = gameLocal.time; + + // Cycle through the channels + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + // Only cycle out the first/main anim of the channel. The rest ar being phased out and don't matter + blend = channels[ i ]; + if ( !blend->IsDone( gameLocal.time ) ) { + if ( blend->Anim() && ( blend->rotateTime > 0 ) && + ( blend->rotateTime <= gameLocal.time ) ) { // HUMANHEAD JRM + + anim = GetAnim( blend->Anim()->Name() ); + event = blend->rotateEvent; + + // gameLocal.Printf( "%s Cycling Anim %s\n", entity->name.c_str(), GetAnim( anim )->FullName() ); + + // blendWeight.Init( currentTime, blendTime, weight, newweight ); + int remainingBlend = ( blend->blendStartTime + blend->blendDuration ) - + gameLocal.time; + if ( remainingBlend < 0 ) { remainingBlend = 0; } + blend->CycleAnim( modelDef, anim, gameLocal.time, remainingBlend ); + entity->BecomeActive( TH_ANIMATE ); + + blend->rotateTime = gameLocal.time + GetAnim( anim )->Length(); + blend->rotateEvent = event; + + entity->PostEventMS( &EV_CheckCycleRotate, GetAnim( anim )->Length() ); + + // Process any change events + if ( event ) { + entity->ProcessEvent( event ); + } + } + } + } +} //. CheckCycleRotate + + +/* +==================== +hhAnimator::CheckThaw +HUMANHEAD nla +==================== +*/ +void hhAnimator::CheckThaw( ) { + hhAnimBlend *blend; + int i, j; + + + // Cycle through the channels + blend = channels[0]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->Anim() ) { + blend->ThawIfTime( gameLocal.time ); + } + } + } + +} //. CheckThaw( ) + +/* +===================== +hhAnimator::CheckTween + Check if the current animation should be tweened to from the previous anim + Inputs: channel - The channel to check the current anim with + Outputs: The time in ms for the tween. 0 if non given. +HUMANHEAD nla +===================== +*/ +int hhAnimator::CheckTween( int channelNum ) { + idStr toAnimName; + idStr fromAnimName = ""; + int timeMS = 0; + int tempInt; + + + // Ensure we have a valid channel number + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CheckTween : channel out of range" ); + } + + + // If no valid anim playing, then nothing to tween. :) + if ( channels[ channelNum ][ 0 ].Anim() == NULL ) { + return( 0 ); + } + else { // Set the to anim name + toAnimName = channels[ channelNum ][ 0 ].AnimName(); + } + + // If a valid anim was playing, get its name + if ( channels[ channelNum ][ 1 ].Anim() != NULL ) { + fromAnimName = channels[ channelNum ][ 1 ].AnimName(); + } + + // We aren't really tweening, head out. = ) + if ( toAnimName == fromAnimName ) { + // gameLocal.Printf("Earlying out!"); + return( 0 ); + } + + // See if a default anim tween time is given + if ( entity->spawnArgs.GetInt( va( "tween2%s", + (const char *) toAnimName ), + "0", tempInt ) ) { + timeMS = tempInt; + // JRM - removed + //gameLocal.Printf( "Got tween2%s of %d\n", (const char *) toAnimName, + // timeMS ); + } + + // See if a anim to anim tween time is given + if ( fromAnimName.Length() && + entity->spawnArgs.GetInt( va( "tween%s2%s", + (const char *) fromAnimName, + (const char *) toAnimName ), + "0", tempInt ) ) { + //! Check that they aren't the same anims + + timeMS = tempInt; + // JRM - removed + //gameLocal.Printf( "Got tween%s2%s of %d\n", (const char *) fromAnimName, + // (const char *) toAnimName, + // timeMS ); + } + + // If a valid tween time is valid, use it + if ( timeMS > 0 ) { + // Freeze the current anim + channels[ channelNum ][ 0 ].Freeze( gameLocal.time, GetEntity(), timeMS ); + + // Setup the new blend times + // NLAMERGE 5 - This really used anymore? + /* + channels[ channelNum ][ 0 ].blendWeight.SetDuration( timeMS ); + channels[ channelNum ][ 1 ].blendWeight.SetDuration( timeMS ); + */ + } + + return( timeMS ); +} //. CheckTween( int ) + + +/* +===================== +hhAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( int channelNum, const idAnim *anim ) { + idAnimBlend * playingAnim; + + + playingAnim = FindAnim( channelNum, anim ); + if ( playingAnim && ! playingAnim->IsDone( gameLocal.time ) ) { + return( true ); + } + + return( false ); +} //. IsAnimPlaying( idAnim * ) + +/* +===================== +ggAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( int channelNum, const char* anim ) { + return IsAnimPlaying( channelNum, GetAnim( GetAnim( anim ) ) ); +} + + +/* +===================== +hhAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( const idAnim *anim ) { + + for( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + if ( IsAnimPlaying( i, anim ) ) { + return( true ); + } + } + + return( false ); +} + + +/* +===================== +hhAnimator::IsAnimPlaying +HUMANHEAD nla +===================== +*/ +bool hhAnimator::IsAnimPlaying( const char* anim ) { + return IsAnimPlaying( GetAnim( GetAnim( anim ) ) ); +} + + +/* +===================== +hhAnimator::FindAnim +HUMANHEAD nla +===================== +*/ +idAnimBlend *hhAnimator::FindAnim( int channelNum, const idAnim *anim ) { + int i; + + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "hhAnimator::FindAnim : channel out of range" ); + } + + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++ ) { + if ( channels[ channelNum ][ i ].Anim() == anim ) { + return &( channels[ channelNum ][ i ] ); + } + } + + return NULL; +} + + +/* +===================== +hhAnimator::GetBlendAnim +HUMANHEAD nla +===================== +*/ +const idAnimBlend * hhAnimator::GetBlendAnim( int channelNum, int index ) const { + + if ( channelNum < 0 || channelNum >= ANIM_NumAnimChannels ) { + gameLocal.Error("Channel out of range"); + } + + return ( ( index < 0 ) || ( index >= ANIM_MaxAnimsPerChannel ) ) ? NULL : &(channels[channelNum][ index ]); +} + + +/* +===================== +hhAnimator::NumBlendAnims +// HUMANHEAD Copied logic from IsAnimating +HUMANHEAD nla +===================== +*/ +int hhAnimator::NumBlendAnims( int currentTime ) { + int i, j; + const idAnimBlend *blend; + // HUMANHEAD nla + int num; + + num = 0; + // HUMANHEAD END + + if ( !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->IsDone( currentTime ) ) { + // HUMANHEAD nla + num++; + // HUMANHEAD END + } + } + } + + return num; +} + + +/* +==================== +hhAnimator::CopyAnimations +HUMANHEAD nla +==================== +*/ +void hhAnimator::CopyAnimations( hhAnimator &source ) { + + + // Loop through each channel + for ( int chan = 0; chan < ANIM_NumAnimChannels; ++chan ) { + + // NLA - Simplifed to the double loop in the Nov 2003 "Big Merge" (c) + // Copy the channel anim info + for ( int anim = 0; anim < ANIM_MaxAnimsPerChannel; + ++anim ) { + + channels[ chan ][ anim ] = source.channels[ chan ][ anim ]; + + } + } + + +} + + +/* +==================== +hhAnimator::CopyPoses +HUMANHEAD nla +==================== +*/ +void hhAnimator::CopyPoses( hhAnimator &source ) { + int i, num; + + // Copy over the joint Mod info + jointMods.DeleteContents( true ); + num = source.jointMods.Num(); + jointMods.SetNum( num ); + for( i = 0; i < num; i++ ) { + jointMods[ i ] = new jointMod_t; + *jointMods[ i ] = *source.jointMods[ i ]; + } + + // Copy over the frameBounds + frameBounds = source.frameBounds; + + // Copy over AF info + AFPoseBlendWeight = source.AFPoseBlendWeight; + + num = source.AFPoseJoints.Num(); + AFPoseJoints.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + AFPoseJoints[ i ] = source.AFPoseJoints[ i ]; + } + + num = source.AFPoseJointMods.Num(); + AFPoseJointMods.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + AFPoseJointMods[ i ] = source.AFPoseJointMods[ i ]; + } + + num = source.AFPoseJointFrame.Num(); + AFPoseJointFrame.SetNum( num ); + for ( int i = 0; i < num; i++ ) { + AFPoseJointFrame[ i ] = source.AFPoseJointFrame[ i ]; + } + + AFPoseBounds = source.AFPoseBounds; + AFPoseTime = source.AFPoseTime; +} + + +/* +==================== +hhAnimator::Freeze +HUMANHEAD nla +==================== +*/ +bool hhAnimator::Freeze( ) { + hhAnimBlend *blend; + + + blend = &( channels[ 0 ][ 0 ] ); + + return( blend->Freeze( gameLocal.time, GetEntity() ) ); + +} + + +/* +==================== +hhAnimator::Thaw +HUMANHEAD nla +==================== +*/ +bool hhAnimator::Thaw( ) { + hhAnimBlend *blend; + + + blend = &( channels[ 0 ][ 0 ] ); + + return( blend->Thaw( gameLocal.time ) ); + +} + + +/* +===================== +hhAnimator::IsAnimating +===================== +*/ +bool hhAnimator::IsAnimating( int currentTime ) const { + int i, j; + const hhAnimBlend *blend; + // HUMANHEAD nla + int numFrozen = 0; + // HUMANHEAD END + + + 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->IsDone( currentTime ) ) { + return true; + } + // HUMANHEAD nla + if ( blend->IsFrozen() ) { numFrozen++; } + // HUMANHEAD END + } + } + + // HUMANHEAD nla + if ( numFrozen > 1 ) { return( true ); } + // HUMANHEAD END + + return false; +} + +/* +===================== +hhAnimator::FrameHasChanged +===================== +*/ +bool hhAnimator::FrameHasChanged( int currentTime ) const { + int i, j; + const hhAnimBlend *blend; + // HUMANHEAD nla - Used to allow for tween (2 frozen) anims to work + int numFrozen = 0; + // HUMANHEAD END + + + 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; + } + // HUMANHEAD nla + if ( blend->IsFrozen() ) { numFrozen++; } + // HUMANHEAD END + } + } + + // HUMANHEAD nla + if ( numFrozen > 1 ) { return( true ); } + // HUMANHEAD END + + if ( forceUpdate && IsAnimating( currentTime ) ) { + return true; + } + + return false; +} + +//================ +//hhAnimator::Save +//================ +void hhAnimator::Save( idSaveGame *savefile ) const { + // Not a subclass of idClass, so this is necessary -mdl + idAnimator::Save( savefile ); + savefile->WriteInt( lastCycleRotate ); +} + +//================ +//hhAnimator::Restore +//================ +void hhAnimator::Restore( idRestoreGame *savefile ) { + // Not a subclass of idClass, so this is necessary -mdl + idAnimator::Restore( savefile ); + savefile->ReadInt( lastCycleRotate ); +} + diff --git a/neo/prey/Prey/prey_animator.h b/neo/prey/Prey/prey_animator.h new file mode 100644 index 00000000..483a8581 --- /dev/null +++ b/neo/prey/Prey/prey_animator.h @@ -0,0 +1,50 @@ + + +#ifndef __PREY_GAME_ANIMATOR_H__ +#define __PREY_GAME_ANIMATOR_H__ + +class hhAnimator : public idAnimator { + public: + + hhAnimator(); + + // Overridden methods + bool FrameHasChanged( int animtime ) const; + bool IsAnimating( int currentTime ) const; + + // Unique methods + void CycleAnim( int channelNum, int anim, int currenttime, int blendtime, const idEventDef *pEvent = NULL ); + void CheckCycleRotate(); + void CheckThaw(); + int CheckTween( int channelNum ); + + bool IsAnimPlaying( int channelNum, const idAnim *anim ); + //bool IsAnimPlaying( int channelNum, int animNum ) { return( IsAnimPlaying( channelNum, GetAnim( animNum ) ) ); } + bool IsAnimPlaying( int channelNum, const char* anim ); + bool IsAnimPlaying( const idAnim *anim ); + //bool IsAnimPlaying( int animNum ) { return( IsAnimPlaying( GetAnim( animNum ) ) ); } + bool IsAnimPlaying( const char* anim ); + bool IsAnimatedModel() const { return modelDef != NULL; } + + int GetNumAnimVariants( int anim ); + + const idAnimBlend *GetBlendAnim( int channelNum, int index ) const; + // Backwards compat functions + idAnimBlend * FindAnim( int channelNum, const idAnim *anim ); // JRM: removed const - couldn't compile + const idAnimBlend *GetBlendAnim( int index ) const { return( GetBlendAnim( ANIMCHANNEL_ALL, index ) ); }; + int NumBlendAnims( int currentTime ); + + void CopyAnimations( hhAnimator &animator ); + void CopyPoses( hhAnimator &animator ); + bool Freeze(); + bool Thaw(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + + int lastCycleRotate; +}; + +#endif /* __PREY_GAME_ANIMATOR_H__ */ diff --git a/neo/prey/Prey/prey_baseweapons.cpp b/neo/prey/Prey/prey_baseweapons.cpp new file mode 100644 index 00000000..b69cc92a --- /dev/null +++ b/neo/prey/Prey/prey_baseweapons.cpp @@ -0,0 +1,2525 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define WEAPON_DEBUG if(g_debugWeapon.GetBool()) gameLocal.Warning + +/*********************************************************************** + + hhWeapon + +***********************************************************************/ +const idEventDef EV_PlayAnimWhenReady( "playAnimWhenReady", "s" ); +const idEventDef EV_Weapon_Aside( "" ); +const idEventDef EV_Weapon_EjectAltBrass( "ejectAltBrass" ); + +const idEventDef EV_Weapon_HasAmmo( "hasAmmo", "", 'd' ); +const idEventDef EV_Weapon_HasAltAmmo( "hasAltAmmo", "", 'd' ); + +const idEventDef EV_Weapon_GetFireDelay( "getFireDelay", "", 'f' ); +const idEventDef EV_Weapon_GetAltFireDelay( "getAltFireDelay", "", 'f' ); +const idEventDef EV_Weapon_GetSpread( "getSpread", "", 'f' ); +const idEventDef EV_Weapon_GetAltSpread( "getAltSpread", "", 'f' ); +const idEventDef EV_Weapon_GetString( "getString", "s", 's' ); +const idEventDef EV_Weapon_GetAltString( "getAltString", "s", 's' ); + +const idEventDef EV_Weapon_AddToAltClip( "addToAltClip", "f" ); +const idEventDef EV_Weapon_AltAmmoInClip( "altAmmoInClip", "", 'f' ); +const idEventDef EV_Weapon_AltAmmoAvailable( "altAmmoAvailable", "", 'f' ); +const idEventDef EV_Weapon_AltClipSize( "altClipSize", "", 'f' ); + +const idEventDef EV_Weapon_FireAltProjectiles( "fireAltProjectiles" ); +const idEventDef EV_Weapon_FireProjectiles( "fireProjectiles" ); + +const idEventDef EV_Weapon_WeaponAside( "weaponAside" ); // nla +const idEventDef EV_Weapon_WeaponPuttingAside( "weaponPuttingAside" ); // nla +const idEventDef EV_Weapon_WeaponUprighting( "weaponUprighting" ); // nla + +const idEventDef EV_Weapon_IsAnimPlaying( "isAnimPlaying", "s", 'd' ); +const idEventDef EV_Weapon_Raise( "" ); // nla - For the hands to post an event to raise the weapons + +const idEventDef EV_Weapon_SetViewAnglesSensitivity( "setViewAnglesSensitivity", "f" ); +const idEventDef EV_Weapon_UseAltAmmo( "useAltAmmo", "d" ); + +const idEventDef EV_Weapon_Hide( "hideWeapon" ); +const idEventDef EV_Weapon_Show( "showWeapon" ); + +CLASS_DECLARATION( hhAnimatedEntity, hhWeapon ) + EVENT( EV_Weapon_FireAltProjectiles, hhWeapon::Event_FireAltProjectiles ) + EVENT( EV_Weapon_FireProjectiles, hhWeapon::Event_FireProjectiles ) + EVENT( EV_PlayAnimWhenReady, hhWeapon::Event_PlayAnimWhenReady ) + EVENT( EV_SpawnFxAlongBone, hhWeapon::Event_SpawnFXAlongBone ) + EVENT( EV_Weapon_EjectAltBrass, hhWeapon::Event_EjectAltBrass ) + EVENT( EV_Weapon_HasAmmo, hhWeapon::Event_HasAmmo ) + EVENT( EV_Weapon_HasAltAmmo, hhWeapon::Event_HasAltAmmo ) + EVENT( EV_Weapon_AddToAltClip, hhWeapon::Event_AddToAltClip ) + EVENT( EV_Weapon_AltAmmoInClip, hhWeapon::Event_AltAmmoInClip ) + EVENT( EV_Weapon_AltAmmoAvailable, hhWeapon::Event_AltAmmoAvailable ) + EVENT( EV_Weapon_AltClipSize, hhWeapon::Event_AltClipSize ) + EVENT( EV_Weapon_GetFireDelay, hhWeapon::Event_GetFireDelay ) + EVENT( EV_Weapon_GetAltFireDelay, hhWeapon::Event_GetAltFireDelay ) + EVENT( EV_Weapon_GetString, hhWeapon::Event_GetString ) + EVENT( EV_Weapon_GetAltString, hhWeapon::Event_GetAltString ) + EVENT( EV_Weapon_Raise, hhWeapon::Event_Raise ) + EVENT( EV_Weapon_WeaponAside, hhWeapon::Event_Weapon_Aside ) + EVENT( EV_Weapon_WeaponPuttingAside, hhWeapon::Event_Weapon_PuttingAside ) + EVENT( EV_Weapon_WeaponUprighting, hhWeapon::Event_Weapon_Uprighting ) + EVENT( EV_Weapon_IsAnimPlaying, hhWeapon::Event_IsAnimPlaying ) + EVENT( EV_Weapon_SetViewAnglesSensitivity, hhWeapon::Event_SetViewAnglesSensitivity ) + EVENT( EV_Weapon_Hide, hhWeapon::Event_HideWeapon ) + EVENT( EV_Weapon_Show, hhWeapon::Event_ShowWeapon ) + + //idWeapon + EVENT( EV_Weapon_State, hhWeapon::Event_WeaponState ) + EVENT( EV_Weapon_WeaponReady, hhWeapon::Event_WeaponReady ) + EVENT( EV_Weapon_WeaponOutOfAmmo, hhWeapon::Event_WeaponOutOfAmmo ) + EVENT( EV_Weapon_WeaponReloading, hhWeapon::Event_WeaponReloading ) + EVENT( EV_Weapon_WeaponHolstered, hhWeapon::Event_WeaponHolstered ) + EVENT( EV_Weapon_WeaponRising, hhWeapon::Event_WeaponRising ) + EVENT( EV_Weapon_WeaponLowering, hhWeapon::Event_WeaponLowering ) + EVENT( EV_Weapon_AddToClip, hhWeapon::Event_AddToClip ) + EVENT( EV_Weapon_AmmoInClip, hhWeapon::Event_AmmoInClip ) + EVENT( EV_Weapon_AmmoAvailable, hhWeapon::Event_AmmoAvailable ) + EVENT( EV_Weapon_ClipSize, hhWeapon::Event_ClipSize ) + EVENT( AI_PlayAnim, hhWeapon::Event_PlayAnim ) + EVENT( AI_PlayCycle, hhWeapon::Event_PlayCycle ) + EVENT( AI_AnimDone, hhWeapon::Event_AnimDone ) + EVENT( EV_Weapon_Next, hhWeapon::Event_Next ) + EVENT( EV_Weapon_Flashlight, hhWeapon::Event_Flashlight ) + EVENT( EV_Weapon_EjectBrass, hhWeapon::Event_EjectBrass ) + EVENT( EV_Weapon_GetOwner, hhWeapon::Event_GetOwner ) + EVENT( EV_Weapon_UseAmmo, hhWeapon::Event_UseAmmo ) + EVENT( EV_Weapon_UseAltAmmo, hhWeapon::Event_UseAltAmmo ) +END_CLASS + +/* +================ +hhWeapon::hhWeapon +================ +*/ +hhWeapon::hhWeapon() { + owner = NULL; + worldModel = NULL; + + thread = NULL; + + //HUMANHEAD: aob + fireController = NULL; + altFireController = NULL; + fl.networkSync = true; + cameraShakeOffset.Zero(); //rww - had to move here to avoid den/nan/blah in spawn + //HUMANHEAD END + + Clear(); +} + +/* +================ +hhWeapon::Spawn +================ +*/ +void hhWeapon::Spawn() { + //idWeapon + if ( !gameLocal.isClient ) + { + worldModel = static_cast< hhAnimatedEntity * >( gameLocal.SpawnEntityType( hhAnimatedEntity::Type, NULL ) ); + worldModel.GetEntity()->fl.networkSync = true; + } + + thread = new idThread(); + thread->ManualDelete(); + thread->ManualControl(); + //idWeapon End + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( NULL, 1.0f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + SetPhysics( &physicsObj ); + + fl.solidForTeam = true; + + // HUMANHEAD: aob + memset( &eyeTraceInfo, 0, sizeof(trace_t) ); + eyeTraceInfo.fraction = 1.0f; + BecomeActive( TH_TICKER ); + handedness = hhMath::hhMax( 1, spawnArgs.GetInt("handedness", "1") ); + fl.neverDormant = true; + // HUMANHEAD END +} + +/* +================ +hhWeapon::~hhWeapon() +================ +*/ +hhWeapon::~hhWeapon() { + SAFE_REMOVE( worldModel ); + SAFE_REMOVE( thread ); + + SAFE_DELETE_PTR( fireController ); + SAFE_DELETE_PTR( altFireController ); + + if ( nozzleFx && nozzleGlowHandle != -1 ) { + gameRenderWorld->FreeLightDef( nozzleGlowHandle ); + } +} + +/* +================ +hhWeapon::SetOwner +================ +*/ +void hhWeapon::SetOwner( idPlayer *_owner ) { + if( !_owner || !_owner->IsType(hhPlayer::Type) ) { + owner = NULL; + return; + } + + owner = static_cast( _owner ); + + if( GetPhysics() && GetPhysics()->IsType(hhPhysics_StaticWeapon::Type) ) { + static_cast(GetPhysics())->SetSelfOwner( owner.GetEntity() ); + } +} + +/* +================ +hhWeapon::Save +================ +*/ +void hhWeapon::Save( idSaveGame *savefile ) const { + savefile->WriteInt( status ); + savefile->WriteObject( thread ); + savefile->WriteString( state ); + savefile->WriteString( idealState ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( animDoneTime ); + + owner.Save( savefile ); + worldModel.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteObject( fireController ); + savefile->WriteObject( altFireController ); + + savefile->WriteTrace( eyeTraceInfo ); + savefile->WriteVec3( cameraShakeOffset ); + savefile->WriteInt( handedness ); + + savefile->WriteVec3( pushVelocity ); + + savefile->WriteString( weaponDef->GetName() ); + + savefile->WriteString( icon ); + + savefile->WriteInt( kick_endtime ); + + savefile->WriteBool( lightOn ); + + savefile->WriteInt( zoomFov ); + + savefile->WriteInt( weaponAngleOffsetAverages ); + savefile->WriteFloat( weaponAngleOffsetScale ); + savefile->WriteFloat( weaponAngleOffsetMax ); + savefile->WriteFloat( weaponOffsetTime ); + savefile->WriteFloat( weaponOffsetScale ); + + savefile->WriteBool( nozzleFx ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( nozzleGlowHandle ); + savefile->WriteJoint( nozzleJointHandle.view ); + savefile->WriteJoint( nozzleJointHandle.world ); + savefile->WriteRenderLight( nozzleGlow ); + savefile->WriteVec3( nozzleGlowColor ); + savefile->WriteMaterial( nozzleGlowShader ); + savefile->WriteFloat( nozzleGlowRadius ); + savefile->WriteVec3( nozzleGlowOffset ); +} + +/* +================ +hhWeapon::Restore +================ +*/ +void hhWeapon::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)status ); + savefile->ReadObject( reinterpret_cast(thread) ); + savefile->ReadString( state ); + savefile->ReadString( idealState ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( animDoneTime ); + + //Re-link script fields + WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); + WEAPON_RAISEWEAPON.LinkTo( scriptObject, "WEAPON_RAISEWEAPON" ); + WEAPON_LOWERWEAPON.LinkTo( scriptObject, "WEAPON_LOWERWEAPON" ); + WEAPON_NEXTATTACK.LinkTo( scriptObject, "nextAttack" ); //HUMANHEAD rww + + //HUMANHEAD: aob + WEAPON_ALTATTACK.LinkTo( scriptObject, "WEAPON_ALTATTACK" ); + WEAPON_ASIDEWEAPON.LinkTo( scriptObject, "WEAPON_ASIDEWEAPON" ); + WEAPON_ALTMODE.LinkTo( scriptObject, "WEAPON_ALTMODE" ); + //HUMANHEAD END + + owner.Restore( savefile ); + worldModel.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadObject( reinterpret_cast< idClass *&> ( fireController ) ); + savefile->ReadObject( reinterpret_cast< idClass *&> ( altFireController ) ); + + savefile->ReadTrace( eyeTraceInfo ); + savefile->ReadVec3( cameraShakeOffset ); + savefile->ReadInt( handedness ); + + savefile->ReadVec3( pushVelocity ); + + idStr objectname; + savefile->ReadString( objectname ); + weaponDef = gameLocal.FindEntityDef( objectname, false ); + if (!weaponDef) { + gameLocal.Error( "Unknown weaponDef: %s\n", objectname ); + } + dict = &(weaponDef->dict); + + savefile->ReadString( icon ); + + savefile->ReadInt( kick_endtime ); + + savefile->ReadBool( lightOn ); + + savefile->ReadInt( zoomFov ); + + savefile->ReadInt( weaponAngleOffsetAverages ); + savefile->ReadFloat( weaponAngleOffsetScale ); + savefile->ReadFloat( weaponAngleOffsetMax ); + savefile->ReadFloat( weaponOffsetTime ); + savefile->ReadFloat( weaponOffsetScale ); + + savefile->ReadBool( nozzleFx ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( nozzleGlowHandle ); + savefile->ReadJoint( nozzleJointHandle.view ); + savefile->ReadJoint( nozzleJointHandle.world ); + savefile->ReadRenderLight( nozzleGlow ); + savefile->ReadVec3( nozzleGlowColor ); + savefile->ReadMaterial( nozzleGlowShader ); + savefile->ReadFloat( nozzleGlowRadius ); + savefile->ReadVec3( nozzleGlowOffset ); +} + +/* +================ +hhWeapon::Clear +================ +*/ +void hhWeapon::Clear( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + WEAPON_ATTACK.Unlink(); + WEAPON_RELOAD.Unlink(); + WEAPON_RAISEWEAPON.Unlink(); + WEAPON_LOWERWEAPON.Unlink(); + WEAPON_NEXTATTACK.Unlink(); //HUMANHEAD rww + + //HUMANHEAD: aob + WEAPON_ALTATTACK.Unlink(); + WEAPON_ASIDEWEAPON.Unlink(); + WEAPON_ALTMODE.Unlink(); + + SAFE_DELETE_PTR( fireController ); + SAFE_DELETE_PTR( altFireController ); + //HUMANEAD END + + //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; + + // nozzle fx + nozzleFx = false; + memset( &nozzleGlow, 0, sizeof( nozzleGlow ) ); + nozzleGlowHandle = -1; + nozzleJointHandle.Clear(); + + memset( &refSound, 0, sizeof( refSound_t ) ); + if ( owner.IsValid() ) { + // don't spatialize the weapon sounds + refSound.listenerId = owner->GetListenerId(); + } + + // clear out the sounds from our spawnargs since we'll copy them from the weapon def + const idKeyValue *kv = spawnArgs.MatchPrefix( "snd_" ); + while( kv ) { + spawnArgs.Delete( kv->GetKey() ); + kv = spawnArgs.MatchPrefix( "snd_" ); + } + + weaponDef = NULL; + dict = NULL; + + kick_endtime = 0; + + icon = ""; + + pushVelocity.Zero(); + + status = WP_HOLSTERED; + state = ""; + idealState = ""; + animBlendFrames = 0; + animDoneTime = 0; + + lightOn = false; + + zoomFov = 90; + + weaponAngleOffsetAverages = 10; //initialize this to default number to prevent infinite loops + + FreeModelDef(); +} + +/* +================ +hhWeapon::InitWorldModel +================ +*/ +void hhWeapon::InitWorldModel( const idDict *dict ) { + idEntity *ent; + + ent = worldModel.GetEntity(); + if ( !ent || !dict ) { + return; + } + + const char *model = dict->GetString( "model_world" ); + const char *attach = dict->GetString( "joint_attach" ); + + if (gameLocal.isMultiplayer) { //HUMANHEAD rww - shadow default based on whether the player shadows + ent->GetRenderEntity()->noShadow = MP_PLAYERNOSHADOW_DEFAULT; + } + + if ( model[0] && attach[0] ) { + ent->Show(); + ent->SetModel( model ); + ent->GetPhysics()->SetContents( 0 ); + ent->GetPhysics()->SetClipModel( NULL, 1.0f ); + ent->BindToJoint( owner.GetEntity(), 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 = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } + } else { + ent->SetModel( "" ); + ent->Hide(); + } +} + +/* +================ +hhWeapon::GetWeaponDef + +HUMANHEAD: aob - this should only be called after the weapon has just been created +================ +*/ +void hhWeapon::GetWeaponDef( const char *objectname ) { + //HUMANHEAD: aob - put logic in helper function + ParseDef( objectname ); + //HUMANHEAD END + + if( owner->inventory.weaponRaised[owner->GetWeaponNum(objectname)] || gameLocal.isMultiplayer ) + ProcessEvent( &EV_Weapon_State, "Raise", 0 ); + else { + ProcessEvent( &EV_Weapon_State, "NewRaise", 0 ); + owner->inventory.weaponRaised[owner->GetWeaponNum(objectname)] = true; + } +} + +/* +================ +hhWeapon::ParseDef + +HUMANHEAD: aob +================ +*/ +void hhWeapon::ParseDef( const char* objectname ) { + if( !objectname || !objectname[ 0 ] ) { + return; + } + + if( !owner.IsValid() ) { + gameLocal.Error( "hhWeapon::ParseDef: no owner" ); + } + + weaponDef = gameLocal.FindEntityDef( objectname, false ); + if (!weaponDef) { + gameLocal.Error( "Unknown weaponDef: %s\n", objectname ); + } + dict = &(weaponDef->dict); + + // setup the world model + InitWorldModel( dict ); + + if (!owner.IsValid() || !owner.GetEntity()) { + gameLocal.Error("NULL owner in hhWeapon::ParseDef!"); + } + + //HUMANHEAD: aob + const idDict* infoDict = gameLocal.FindEntityDefDict( dict->GetString("def_fireInfo"), false ); + if( infoDict ) { + fireController = CreateFireController(); + if( fireController ) { + fireController->Init( infoDict, this, owner.GetEntity() ); + } + } + + infoDict = gameLocal.FindEntityDefDict( dict->GetString("def_altFireInfo"), false ); + if( infoDict ) { + altFireController = CreateAltFireController(); + if( altFireController ) { + altFireController->Init( infoDict, this, owner.GetEntity() ); + } + } + //HUMANHEAD END + + icon = dict->GetString( "inv_icon" ); + + // copy the sounds from the weapon view model def into out spawnargs + const idKeyValue *kv = dict->MatchPrefix( "snd_" ); + while( kv ) { + spawnArgs.Set( kv->GetKey(), kv->GetValue() ); + kv = dict->MatchPrefix( "snd_", kv ); + } + + weaponAngleOffsetAverages = dict->GetInt( "weaponAngleOffsetAverages", "10" ); + weaponAngleOffsetScale = dict->GetFloat( "weaponAngleOffsetScale", "0.1" ); + weaponAngleOffsetMax = dict->GetFloat( "weaponAngleOffsetMax", "10" ); + + weaponOffsetTime = dict->GetFloat( "weaponOffsetTime", "400" ); + weaponOffsetScale = dict->GetFloat( "weaponOffsetScale", "0.002" ); + + zoomFov = dict->GetInt( "zoomFov", "70" ); + + InitScriptObject( dict->GetString("scriptobject") ); + + nozzleFx = weaponDef->dict.GetBool( "nozzleFx", "0" ); + nozzleGlowColor = weaponDef->dict.GetVector("nozzleGlowColor", "1 1 1"); + nozzleGlowRadius = weaponDef->dict.GetFloat("nozzleGlowRadius", "10"); + nozzleGlowOffset = weaponDef->dict.GetVector( "nozzleGlowOffset", "0 0 0" ); + nozzleGlowShader = declManager->FindMaterial( weaponDef->dict.GetString( "mtr_nozzleGlowShader", "" ), false ); + GetJointHandle( weaponDef->dict.GetString( "nozzleJoint", "" ), nozzleJointHandle ); + + //HUMANHEAD bjk + int clipAmmo = owner->inventory.clip[owner->GetWeaponNum(objectname)]; + if ( ( clipAmmo < 0 ) || ( clipAmmo > fireController->ClipSize() ) ) { + // first time using this weapon so have it fully loaded to start + clipAmmo = fireController->ClipSize(); + if ( clipAmmo > fireController->AmmoAvailable() ) { + clipAmmo = fireController->AmmoAvailable(); + } + } + fireController->AddToClip(clipAmmo); + + WEAPON_ALTMODE = owner->inventory.altMode[owner->GetWeaponNum(objectname)]; + //HUMANHEAD END + + Show(); +} + +/* +================ +idWeapon::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 hhWeapon::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +================ +hhWeapon::InitScriptObject +================ +*/ +void hhWeapon::InitScriptObject( const char* objectType ) { + if( !objectType || !objectType[0] ) { + gameLocal.Error( "No scriptobject set on '%s'.", dict->GetString("classname") ); + } + + if( !idStr::Icmp(scriptObject.GetTypeName(), objectType) ) { + //Same script object, don't reload it + return; + } + + // setup script object + if( !scriptObject.SetType(objectType) ) { + gameLocal.Error( "Script object '%s' not found on weapon '%s'.", objectType, dict->GetString("classname") ); + } + + WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); + WEAPON_RAISEWEAPON.LinkTo( scriptObject, "WEAPON_RAISEWEAPON" ); + WEAPON_LOWERWEAPON.LinkTo( scriptObject, "WEAPON_LOWERWEAPON" ); + WEAPON_NEXTATTACK.LinkTo( scriptObject, "nextAttack" ); //HUMANHEAD rww + + //HUMANHEAD: aob + WEAPON_ALTATTACK.LinkTo( scriptObject, "WEAPON_ALTATTACK" ); + WEAPON_ASIDEWEAPON.LinkTo( scriptObject, "WEAPON_ASIDEWEAPON" ); + WEAPON_ALTMODE.LinkTo( scriptObject, "WEAPON_ALTMODE" ); + //HUMANHEAD END + + // call script object's constructor + ConstructScriptObject(); +} + +/*********************************************************************** + + GUIs + +***********************************************************************/ + +/* +================ +hhWeapon::Icon +================ +*/ +const char *hhWeapon::Icon( void ) const { + return icon; +} + +/* +================ +hhWeapon::UpdateGUI +================ +*/ +void hhWeapon::UpdateGUI() { + if ( !renderEntity.gui[0] ) { + return; + } + + if ( status == WP_HOLSTERED ) { + return; + } + + int ammoamount = AmmoAvailable(); + int altammoamount = AltAmmoAvailable(); + + // show remaining ammo + renderEntity.gui[ 0 ]->SetStateInt( "ammoamount", ammoamount ); + renderEntity.gui[ 0 ]->SetStateInt( "altammoamount", altammoamount ); + renderEntity.gui[ 0 ]->SetStateBool( "ammolow", ammoamount > 0 && ammoamount <= LowAmmo() ); + renderEntity.gui[ 0 ]->SetStateBool( "altammolow", altammoamount > 0 && altammoamount <= LowAltAmmo() ); + renderEntity.gui[ 0 ]->SetStateBool( "ammoempty", ( ammoamount == 0 ) ); + renderEntity.gui[ 0 ]->SetStateBool( "altammoempty", ( altammoamount == 0 ) ); + renderEntity.gui[ 0 ]->SetStateInt( "clipammoAmount", AmmoInClip() ); + renderEntity.gui[ 0 ]->SetStateInt( "clipaltammoAmount", AltAmmoInClip() ); + renderEntity.gui[ 0 ]->StateChanged(gameLocal.time); +} + +/*********************************************************************** + + Model and muzzleflash + +***********************************************************************/ +/* +================ +hhWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool hhWeapon::GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ) { + weaponJointHandle_t handle; + + GetJointHandle( jointName, handle ); + return GetJointWorldTransform( handle, offset, axis ); +} + +/* +================ +hhWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool hhWeapon::GetJointWorldTransform( const weaponJointHandle_t& handle, idVec3 &offset, idMat3 &axis, bool muzzleOnly ) { + //FIXME: this seems to work but may need revisiting + //FIXME: totally forgot about mirrors and portals. This may not work. + if( (!pm_thirdPerson.GetBool() && owner == gameLocal.GetLocalPlayer()) || (gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == owner->entityNumber) || (gameLocal.isServer && !muzzleOnly)) { //rww - mp server should always create projectiles from the viewmodel + // view model + if ( hhAnimatedEntity::GetJointWorldTransform(handle.view, offset, axis) ) { + return true; + } + } else { + // world model + if ( worldModel.IsValid() && worldModel.GetEntity()->GetJointWorldTransform(handle.world, offset, axis) ) { + return true; + } + } + offset = GetOrigin(); + axis = GetAxis(); + return false; +} + +/* +================ +hhWeapon::GetJointHandle + +HUMANHEAD: aob +================ +*/ +void hhWeapon::GetJointHandle( const char* jointName, weaponJointHandle_t& handle ) { + if( dict ) { + handle.view = GetAnimator()->GetJointHandle( jointName ); + } + + if( worldModel.IsValid() ) { + handle.world = worldModel->GetAnimator()->GetJointHandle( jointName ); + } +} + +/* +================ +hhWeapon::SetPushVelocity +================ +*/ +void hhWeapon::SetPushVelocity( const idVec3 &pushVelocity ) { + this->pushVelocity = pushVelocity; +} + + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +hhWeapon::Raise +================ +*/ +void hhWeapon::Raise( void ) { + WEAPON_RAISEWEAPON = true; + WEAPON_ASIDEWEAPON = false; +} + +/* +================ +hhWeapon::PutAway +================ +*/ +void hhWeapon::PutAway( void ) { + //hasBloodSplat = false; + WEAPON_LOWERWEAPON = true; + WEAPON_RAISEWEAPON = false; //HUMANHEAD bjk PCF 5-4-06 : fix for weapons being up after death + WEAPON_ASIDEWEAPON = false; + WEAPON_ATTACK = false; + WEAPON_ALTATTACK = false; +} + + +/* +================ +hhWeapon::PutAside +================ +*/ +void hhWeapon::PutAside( void ) { + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_LOWERWEAPON = false; + //WEAPON_RAISEWEAPON = false; + WEAPON_ASIDEWEAPON = true; + WEAPON_ATTACK = false; + WEAPON_ALTATTACK = false; +} + +/* +================ +hhWeapon::PutUpright +================ +*/ +void hhWeapon::PutUpright( void ) { + WEAPON_ASIDEWEAPON = false; +} + +/* +================ +hhWeapon::SnapDown + +HUMANHEAD: aob +================ +*/ +void hhWeapon::SnapDown() { + ProcessEvent( &EV_Weapon_State, "Down", 0 ); +} + +/* +================ +hhWeapon::SnapUp + +HUMANHEAD: aob +================ +*/ +void hhWeapon::SnapUp() { + ProcessEvent( &EV_Weapon_State, "Up", 0 ); +} + +/* +================ +hhWeapon::Reload +================ +*/ +void hhWeapon::Reload( void ) { + WEAPON_RELOAD = true; +} + +/* +================ +hhWeapon::HideWeapon +================ +*/ +void hhWeapon::HideWeapon( void ) { + Hide(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } +} + +/* +================ +hhWeapon::ShowWeapon +================ +*/ +void hhWeapon::ShowWeapon( void ) { + Show(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Show(); + } +} + +/* +================ +hhWeapon::HideWorldModel +================ +*/ +void hhWeapon::HideWorldModel( void ) { + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } +} + +/* +================ +hhWeapon::ShowWorldModel +================ +*/ +void hhWeapon::ShowWorldModel( void ) { + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Show(); + } +} + +/* +================ +hhWeapon::OwnerDied +================ +*/ +void hhWeapon::OwnerDied( void ) { + Hide(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->Hide(); + } +} + +/* +================ +hhWeapon::BeginAltAttack +================ +*/ +void hhWeapon::BeginAltAttack( void ) { + WEAPON_ALTATTACK = true; + + //if ( status != WP_OUTOFAMMO ) { + // lastAttack = gameLocal.time; + //} +} + +/* +================ +hhWeapon::EndAltAttack +================ +*/ +void hhWeapon::EndAltAttack( void ) { + WEAPON_ALTATTACK = false; + +} + +/* +================ +hhWeapon::BeginAttack +================ +*/ +void hhWeapon::BeginAttack( void ) { + //if ( status != WP_OUTOFAMMO ) { + // lastAttack = gameLocal.time; + //} + + WEAPON_ATTACK = true; +} + +/* +================ +hhWeapon::EndAttack +================ +*/ +void hhWeapon::EndAttack( void ) { + if ( !WEAPON_ATTACK.IsLinked() ) { + return; + } + if ( WEAPON_ATTACK ) { + WEAPON_ATTACK = false; + } +} + +/* +================ +hhWeapon::isReady +================ +*/ +bool hhWeapon::IsReady( void ) const { + return !IsHidden() && ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +hhWeapon::IsReloading +================ +*/ +bool hhWeapon::IsReloading( void ) const { + return ( status == WP_RELOAD ); +} + +/* +================ +hhWeapon::IsChangable +================ +*/ +bool hhWeapon::IsChangable( void ) const { + //HUMANHEAD: aob - same as IsReady except w/o IsHidden check + //Allows us to switch weapons while they are hidden + //Hope this doesn't fuck to many things + return ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +hhWeapon::IsHolstered +================ +*/ +bool hhWeapon::IsHolstered( void ) const { + return ( status == WP_HOLSTERED ); +} + + +/* +================ +hhWeapon::IsLowered +================ +*/ +bool hhWeapon::IsLowered( void ) const { + return ( status == WP_HOLSTERED ) || ( status == WP_LOWERING ); +} + + +/* +================ +hhWeapon::IsRising +================ +*/ +bool hhWeapon::IsRising( void ) const { + return ( status == WP_RISING ); +} + + +/* +================ +hhWeapon::IsAside +================ +*/ +bool hhWeapon::IsAside( void ) const { + return ( status == WP_ASIDE ); +} + + +/* +================ +hhWeapon::ShowCrosshair +================ +*/ +bool hhWeapon::ShowCrosshair( void ) const { + //HUMANHEAD: aob - added cinematic check + return !( state == idStr( WP_RISING ) || state == idStr( WP_LOWERING ) || state == idStr( WP_HOLSTERED ) ) && !owner->InCinematic(); +} + +/* +===================== +hhWeapon::DropItem +===================== +*/ +idEntity* hhWeapon::DropItem( const idVec3 &velocity, int activateDelay, int removeDelay, bool died ) { + if ( !weaponDef || !worldModel.GetEntity() ) { + return NULL; + } + + const char *classname = weaponDef->dict.GetString( "def_dropItem" ); + if ( !classname[0] ) { + return NULL; + } + StopSound( SND_CHANNEL_BODY, !died ); //HUMANHEAD rww - on death, do not broadcast the stop, because the weapon itself is about to be removed + return idMoveableItem::DropItem( classname, worldModel.GetEntity()->GetPhysics()->GetOrigin(), worldModel.GetEntity()->GetPhysics()->GetAxis(), velocity, activateDelay, removeDelay ); +} + +/* +===================== +hhWeapon::CanDrop +===================== +*/ +bool hhWeapon::CanDrop( void ) const { + if ( !weaponDef || !worldModel.GetEntity() ) { + return false; + } + const char *classname = weaponDef->dict.GetString( "def_dropItem" ); + if ( !classname[ 0 ] ) { + return false; + } + return true; +} + +/*********************************************************************** + + Script state management + +***********************************************************************/ + +/* +===================== +hhWeapon::SetState +===================== +*/ +void hhWeapon::SetState( const char *statename, int blendFrames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + thread->CallFunction( this, func, true ); + state = statename; + + animBlendFrames = blendFrames; + if ( g_debugWeapon.GetBool() ) { + gameLocal.Printf( "%d: weapon state : %s\n", gameLocal.time, statename ); + } + + idealState = ""; +} + +/*********************************************************************** + + Visual presentation + +***********************************************************************/ + +/* +================ +hhWeapon::MuzzleRise + +HUMANHEAD: aob +================ +*/ +void hhWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + if( fireController ) { + fireController->CalculateMuzzleRise( origin, axis ); + } + + if( altFireController ) { + altFireController->CalculateMuzzleRise( origin, axis ); + } +} + +/* +================ +hhWeapon::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 *hhWeapon::ConstructScriptObject( void ) { + const function_t *constructor; + + //HUMANHEAD: aob + if( !thread ) { + return thread; + } + //HUMANHEAD END + + thread->EndThread(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( !constructor ) { + gameLocal.Error( "Missing constructor on '%s' for weapon", scriptObject.GetTypeName() ); + } + + // init the script object's data + scriptObject.ClearObject(); + thread->CallFunction( this, constructor, true ); + thread->Execute(); + + return thread; +} + +/* +================ +hhWeapon::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 hhWeapon::DeconstructScriptObject( void ) { + const function_t *destructor; + + if ( !thread ) { + return; + } + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + thread->EndThread(); + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and end + thread->CallFunction( this, destructor, true ); + thread->Execute(); + thread->EndThread(); + } + + // clear out the object's memory + scriptObject.ClearObject(); +} + +/* +================ +hhWeapon::UpdateScript +================ +*/ +void hhWeapon::UpdateScript( void ) { + int count; + + if ( !IsLinked() ) { + return; + } + + // only update the script on new frames + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( idealState.Length() ) { + SetState( idealState, animBlendFrames ); + } + + // update script state, which may call Event_LaunchProjectiles, among other things + count = 10; + while( ( thread->Execute() || idealState.Length() ) && count-- ) { + // happens for weapons with no clip (like grenades) + if ( idealState.Length() ) { + SetState( idealState, animBlendFrames ); + } + } + + WEAPON_RELOAD = false; +} + +/* +================ +hhWeapon::PresentWeapon +================ +*/ +void hhWeapon::PresentWeapon( bool showViewModel ) { + UpdateScript(); + + //HUMANHEAD rww - added this gui owner logic here + bool allowGuiUpdate = true; + if ( owner.IsValid() && gameLocal.localClientNum != owner->entityNumber ) { + // if updating the hud for a followed client + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != owner->entityNumber ) { + allowGuiUpdate = false; + } + } else { + allowGuiUpdate = false; + } + } + if (allowGuiUpdate) { + UpdateGUI(); + } + + // update animation + UpdateAnimation(); + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = owner->entityNumber+1; + + // crunch the depth range so it never pokes into walls this breaks the machine gun gui + renderEntity.weaponDepthHack = true; + + // present the model + if ( showViewModel ) { + Present(); + } else { + FreeModelDef(); + } + + if ( worldModel.GetEntity() && worldModel.GetEntity()->GetRenderEntity() ) { + // deal with the third-person visible world model + // don't show shadows of the world model in first person +#ifdef HUMANHEAD + if ( gameLocal.isMultiplayer || (g_showPlayerShadow.GetBool() && pm_modelView.GetInteger() == 1) || pm_thirdPerson.GetBool() + || (pm_modelView.GetInteger() == 2 && owner->health <= 0)) { + worldModel->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + worldModel->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } +#else + + // deal with the third-person visible world model + // don't show shadows of the world model in first person + if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel.GetEntity()->GetRenderEntity()->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + } + } +#endif // HUMANHEAD pdm + } + + // update the muzzle flash light, so it moves with the gun + //HUMANHEAD: aob + if( fireController ) { + fireController->UpdateMuzzleFlash(); + } + + if( altFireController ) { + altFireController->UpdateMuzzleFlash(); + } + //HUMANHEAD END + + UpdateNozzleFx(); // expensive + + UpdateSound(); +} + + +/* +================ +hhWeapon::EnterCinematic +================ +*/ +void hhWeapon::EnterCinematic( void ) { + StopSound( SND_CHANNEL_ANY, false ); + + if ( IsLinked() ) { + SetState( "EnterCinematic", 0 ); + thread->Execute(); + + WEAPON_ATTACK = false; + WEAPON_RELOAD = false; +// WEAPON_NETRELOAD = false; +// WEAPON_NETENDRELOAD = false; + WEAPON_RAISEWEAPON = false; + WEAPON_LOWERWEAPON = false; + } + + //disabled = true; + + LowerWeapon(); +} + +/* +================ +hhWeapon::ExitCinematic +================ +*/ +void hhWeapon::ExitCinematic( void ) { + //disabled = false; + + if ( IsLinked() ) { + SetState( "ExitCinematic", 0 ); + thread->Execute(); + } + + RaiseWeapon(); +} + +/* +================ +hhWeapon::NetCatchup +================ +*/ +void hhWeapon::NetCatchup( void ) { + if ( IsLinked() ) { + SetState( "NetCatchup", 0 ); + thread->Execute(); + } +} + +/* +================ +hhWeapon::GetClipBits +================ +*/ +int hhWeapon::GetClipBits(void) const { + return ASYNC_PLAYER_INV_CLIP_BITS; +} + +/* +================ +hhWeapon::GetZoomFov +================ +*/ +int hhWeapon::GetZoomFov( void ) { + return zoomFov; +} + +/* +================ +hhWeapon::GetWeaponAngleOffsets +================ +*/ +void hhWeapon::GetWeaponAngleOffsets( int *average, float *scale, float *max ) { + *average = weaponAngleOffsetAverages; + *scale = weaponAngleOffsetScale; + *max = weaponAngleOffsetMax; +} + +/* +================ +hhWeapon::GetWeaponTimeOffsets +================ +*/ +void hhWeapon::GetWeaponTimeOffsets( float *time, float *scale ) { + *time = weaponOffsetTime; + *scale = weaponOffsetScale; +} + + +/*********************************************************************** + + Ammo + +**********************************************************************/ + +/* +================ +hhWeapon::WriteToSnapshot +================ +*/ +void hhWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { +#if 0 + //FIXME: need to add altFire stuff too + if( fireController ) { + msg.WriteBits( fireController->AmmoInClip(), GetClipBits() ); + } else { + msg.WriteBits( 0, GetClipBits() ); + } + msg.WriteBits(worldModel.GetSpawnId(), 32); +#else //rww - forget this silliness, let's do this the Right Way. + msg.WriteBits(owner.GetSpawnId(), 32); + msg.WriteBits(worldModel.GetSpawnId(), 32); + + if (fireController) { + fireController->WriteToSnapshot(msg); + } + else { //rwwFIXME this is an ugly hack + msg.WriteBits(0, 32); + msg.WriteBits(0, 32); + msg.WriteBits(0, GetClipBits()); + } + if (altFireController) { + altFireController->WriteToSnapshot(msg); + } + else { //rwwFIXME this is an ugly hack + msg.WriteBits(0, 32); + msg.WriteBits(0, 32); + msg.WriteBits(0, GetClipBits()); + } + + msg.WriteBits(WEAPON_ASIDEWEAPON, 1); + //HUMANHEAD PCF rww 05/09/06 - no need to sync these values + /* + msg.WriteBits(WEAPON_LOWERWEAPON, 1); + msg.WriteBits(WEAPON_RAISEWEAPON, 1); + */ + + WriteBindToSnapshot(msg); +#endif +} + +/* +================ +hhWeapon::ReadFromSnapshot +================ +*/ +void hhWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { +#if 0 + //FIXME: need to add altFire stuff too + if( fireController ) { + fireController->AddToClip( msg.ReadBits(GetClipBits()) ); + } else { + msg.ReadBits( GetClipBits() ); + } + + worldModel.SetSpawnId(msg.ReadBits(32)); +#else + bool wmInit = false; + + owner.SetSpawnId(msg.ReadBits(32)); + + if (worldModel.SetSpawnId(msg.ReadBits(32))) { //do this once we finally get the entity in the snapshot + wmInit = true; + } + + //rwwFIXME this is a little hacky, i think. is there a way to do it in ::Spawn? but, the weapon doesn't know its owner at that point. + if (!dict) { + if (!owner.IsValid() || !owner.GetEntity()) { + gameLocal.Error("NULL owner in hhWeapon::ReadFromSnapshot!"); + } + + assert(owner.IsValid()); + + GetWeaponDef(spawnArgs.GetString("classname")); + //owner->ForceWeapon(this); it doesn't like this. + } + assert(fireController); + assert(altFireController); + + if (wmInit) { //need to do this once the ent is valid on the client + InitWorldModel( dict ); + GetJointHandle( weaponDef->dict.GetString( "nozzleJoint", "" ), nozzleJointHandle ); + fireController->UpdateWeaponJoints(); + altFireController->UpdateWeaponJoints(); + } + + fireController->ReadFromSnapshot(msg); + altFireController->ReadFromSnapshot(msg); + + WEAPON_ASIDEWEAPON = !!msg.ReadBits(1); + //HUMANHEAD PCF rww 05/09/06 - no need to sync these values + /* + WEAPON_LOWERWEAPON = !!msg.ReadBits(1); + WEAPON_RAISEWEAPON = !!msg.ReadBits(1); + */ + + ReadBindFromSnapshot(msg); +#endif +} + +/* +================ +hhWeapon::ClientPredictionThink +================ +*/ +void hhWeapon::ClientPredictionThink( void ) { + Think(); +} + +/*********************************************************************** + + Script events + +***********************************************************************/ + +/* +hhWeapon::Event_Raise +*/ +void hhWeapon::Event_Raise() { + Raise(); +} + + +/* +=============== +hhWeapon::Event_WeaponState +=============== +*/ +void hhWeapon::Event_WeaponState( const char *statename, int blendFrames ) { + 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; + animBlendFrames = blendFrames; + thread->DoneProcessing(); +} + +/* +=============== +hhWeapon::Event_WeaponReady +=============== +*/ +void hhWeapon::Event_WeaponReady( void ) { + status = WP_READY; + WEAPON_RAISEWEAPON = false; + + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_LOWERWEAPON = false; + //WEAPON_ASIDEWEAPON = false; +} + +/* +=============== +hhWeapon::Event_WeaponOutOfAmmo +=============== +*/ +void hhWeapon::Event_WeaponOutOfAmmo( void ) { + status = WP_OUTOFAMMO; + WEAPON_RAISEWEAPON = false; + + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_LOWERWEAPON = false; + //WEAPON_ASIDEWEAPON = false; +} + +/* +=============== +hhWeapon::Event_WeaponReloading +=============== +*/ +void hhWeapon::Event_WeaponReloading( void ) { + status = WP_RELOAD; +} + +/* +=============== +hhWeapon::Event_WeaponHolstered +=============== +*/ +void hhWeapon::Event_WeaponHolstered( void ) { + status = WP_HOLSTERED; + WEAPON_LOWERWEAPON = false; +} + +/* +=============== +hhWeapon::Event_WeaponRising +=============== +*/ +void hhWeapon::Event_WeaponRising( void ) { + status = WP_RISING; + WEAPON_LOWERWEAPON = false; + + //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state + //WEAPON_ASIDEWEAPON = false; + + owner->WeaponRisingCallback(); +} + + +/* +=============== +hhWeapon::Event_WeaponAside +=============== +*/ +void hhWeapon::Event_Weapon_Aside( void ) { + status = WP_ASIDE; +} + +/* +=============== +hhWeapon::Event_Weapon_PuttingAside +=============== +*/ +void hhWeapon::Event_Weapon_PuttingAside( void ) { + status = WP_PUTTING_ASIDE; +} + +/* +=============== +hhWeapon::Event_Weapon_Uprighting +=============== +*/ +void hhWeapon::Event_Weapon_Uprighting( void ) { + status = WP_UPRIGHTING; +} + +/* +=============== +hhWeapon::Event_WeaponLowering +=============== +*/ +void hhWeapon::Event_WeaponLowering( void ) { + status = WP_LOWERING; + WEAPON_RAISEWEAPON = false; + + //HUMANHEAD: aob + WEAPON_ASIDEWEAPON = false; + //HUMANHEAD END + + owner->WeaponLoweringCallback(); +} + +/* +=============== +hhWeapon::Event_AddToClip +=============== +*/ +void hhWeapon::Event_AddToClip( int amount ) { + if( fireController ) { + fireController->AddToClip( amount ); + } +} + +/* +=============== +hhWeapon::Event_AmmoInClip +=============== +*/ +void hhWeapon::Event_AmmoInClip( void ) { + int ammo = AmmoInClip(); + idThread::ReturnFloat( ammo ); +} + +/* +=============== +hhWeapon::Event_AmmoAvailable +=============== +*/ +void hhWeapon::Event_AmmoAvailable( void ) { + int ammoAvail = AmmoAvailable(); + idThread::ReturnFloat( ammoAvail ); +} + +/* +=============== +hhWeapon::Event_ClipSize +=============== +*/ +void hhWeapon::Event_ClipSize( void ) { + idThread::ReturnFloat( ClipSize() ); +} + +/* +=============== +hhWeapon::Event_PlayAnim +=============== +*/ +void hhWeapon::Event_PlayAnim( int channel, const char *animname ) { + int anim = 0; + + anim = GetAnimator()->GetAnim( animname ); + if ( !anim ) { + //HUMANHEAD: aob + WEAPON_DEBUG( "missing '%s' animation on '%s'", animname, name.c_str() ); + //HUMANHEAD END + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } else { + //Show(); AOB - removed so we can use hidden weapons Hope this doesn't fuck to many things + GetAnimator()->PlayAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = GetAnimator()->CurrentAnim( channel )->GetEndTime(); + if ( worldModel.GetEntity() ) { + anim = worldModel.GetEntity()->GetAnimator()->GetAnim( animname ); + if ( anim ) { + worldModel.GetEntity()->GetAnimator()->PlayAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + } + } + } + animBlendFrames = 0; +} + +/* +=============== +hhWeapon::Event_AnimDone +=============== +*/ +void hhWeapon::Event_AnimDone( int channel, int blendFrames ) { + if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +hhWeapon::Event_Next +================ +*/ +void hhWeapon::Event_Next( void ) { + // change to another weapon if possible + owner->NextBestWeapon(); +} + +/* +================ +hhWeapon::Event_Flashlight +================ +*/ +void hhWeapon::Event_Flashlight( int enable ) { +/* + if ( enable ) { + lightOn = true; + MuzzleFlashLight(); + } else { + lightOn = false; + muzzleFlashEnd = 0; + } +*/ +} +/* +================ +hhWeapon::Event_FireProjectiles + +HUMANHEAD: aob +================ +*/ +void hhWeapon::Event_FireProjectiles() { + //HUMANHEAD: aob - moved logic to this helper function + LaunchProjectiles( fireController ); + //HUMANHEAD END +} + +/* +================ +hhWeapon::Event_GetOwner +================ +*/ +void hhWeapon::Event_GetOwner( void ) { + idThread::ReturnEntity( owner.GetEntity() ); +} + +/* +=============== +hhWeapon::Event_UseAmmo +=============== +*/ +void hhWeapon::Event_UseAmmo( int amount ) { + UseAmmo(); +} + +/* +=============== +hhWeapon::Event_UseAltAmmo +=============== +*/ +void hhWeapon::Event_UseAltAmmo( int amount ) { + UseAltAmmo(); +} + +/* +================ +hhWeapon::RestoreGUI + +HUMANHEAD: aob +================ +*/ +void hhWeapon::RestoreGUI( const char* guiKey, idUserInterface** gui ) { + if( guiKey && guiKey[0] && gui ) { + AddRenderGui( dict->GetString(guiKey), gui, dict ); + } +} + +/* +================ +hhWeapon::Think +================ +*/ +void hhWeapon::Think() { + if( thinkFlags & TH_TICKER ) { + if (owner.IsValid()) { + Ticker(); + } + } +} + +/* +================ +hhWeapon::SpawnWorldModel +================ +*/ +idEntity* hhWeapon::SpawnWorldModel( const char* worldModelDict, idActor* _owner ) { +/* + assert( _owner && worldModelDict ); + + idEntity* pEntity = NULL; + idDict* pArgs = declManager->FindEntityDefDict( worldModelDict, false ); + if( !pArgs ) { return NULL; } + + idStr ownerWeaponBindBone = _owner->spawnArgs.GetString( "bone_weapon_bind" ); + idStr attachBone = pArgs->GetString( "attach" ); + if( attachBone.Length() && ownerWeaponBindBone.Length() ) { + pEntity = gameLocal.SpawnEntityType( idEntity::Type, pArgs ); + HH_ASSERT( pEntity ); + + pEntity->GetPhysics()->SetContents( 0 ); + pEntity->GetPhysics()->DisableClip(); + pEntity->MoveJointToJoint( attachBone.c_str(), _owner, ownerWeaponBindBone.c_str() ); + pEntity->AlignJointToJoint( attachBone.c_str(), _owner, ownerWeaponBindBone.c_str() ); + pEntity->BindToJoint( _owner, ownerWeaponBindBone.c_str(), true ); + + // supress model in player views and in any views on the weapon itself + pEntity->renderEntity.suppressSurfaceInViewID = _owner->entityNumber + 1; + } +*/ + return NULL; +} + +/* +================ +hhWeapon::SetModel +================ +*/ +void hhWeapon::SetModel( const char *modelname ) { + assert( modelname ); + + if ( modelDefHandle >= 0 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + + hhAnimatedEntity::SetModel( modelname ); + + // hide the model until an animation is played + Hide(); +} + +/* +================ +hhWeapon::FreeModelDef +================ +*/ +void hhWeapon::FreeModelDef() { + hhAnimatedEntity::FreeModelDef(); +} + +/* +================ +hhWeapon::GetPhysicsToVisualTransform +================ +*/ +bool hhWeapon::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + bool bResult = hhAnimatedEntity::GetPhysicsToVisualTransform( origin, axis ); + + assert(!FLOAT_IS_NAN(cameraShakeOffset.x)); + assert(!FLOAT_IS_NAN(cameraShakeOffset.y)); + assert(!FLOAT_IS_NAN(cameraShakeOffset.z)); + + if( !cameraShakeOffset.Compare(vec3_zero, VECTOR_EPSILON) ) { + origin = (bResult) ? cameraShakeOffset + origin : cameraShakeOffset; + origin *= GetPhysics()->GetAxis().Transpose(); + if( !bResult ) { axis = mat3_identity; } + return true; + } + + return bResult; +} + +/* +================ +hhWeapon::GetMasterDefaultPosition +================ +*/ +void hhWeapon::GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idActor* actor = NULL; + idEntity* master = GetBindMaster(); + + if( master ) { + if( master->IsType(idActor::Type) ) { + actor = static_cast( master ); + actor->DetermineOwnerPosition( masterOrigin, masterAxis ); + + masterOrigin = actor->ApplyLandDeflect( masterOrigin, 1.1f ); + } else { + hhAnimatedEntity::GetMasterDefaultPosition( masterOrigin, masterAxis ); + } + } +} + +/* +================ +hhWeapon::SetShaderParm +================ +*/ +void hhWeapon::SetShaderParm( int parmnum, float value ) { + hhAnimatedEntity::SetShaderParm(parmnum, value); + if ( worldModel.IsValid() ) { + worldModel->SetShaderParm(parmnum, value); + } +} + +/* +================ +hhWeapon::SetSkin +================ +*/ +void hhWeapon::SetSkin( const idDeclSkin *skin ) { + hhAnimatedEntity::SetSkin(skin); + if ( worldModel.IsValid() ) { + worldModel->SetSkin(skin); + } +} + +/* +================ +hhWeapon::PlayCycle +================ +*/ +void hhWeapon::Event_PlayCycle( int channel, const char *animname ) { + int anim; + + anim = GetAnimator()->GetAnim( animname ); + if ( !anim ) { + //HUMANHEAD: aob + WEAPON_DEBUG( "missing '%s' animation on '%s'", animname, name.c_str() ); + //HUMANHEAD END + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = 0; + } else { + //Show();AOB - removed so we can use hidden weapons Hope this doesn't fuck to many things + + // NLANOTE - Used to be CARandom + GetAnimator()->CycleAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + animDoneTime = GetAnimator()->CurrentAnim( channel )->GetEndTime(); + if ( worldModel.GetEntity() ) { + anim = worldModel.GetEntity()->GetAnimator()->GetAnim( animname ); + // NLANOTE - Used to be CARandom + worldModel.GetEntity()->GetAnimator()->CycleAnim( channel, anim, gameLocal.GetTime(), FRAME2MS( animBlendFrames ) ); + } + } + animBlendFrames = 0; +} + +/* +================ +hhWeapon::Event_IsAnimPlaying +================ +*/ +void hhWeapon::Event_IsAnimPlaying( const char *animname ) { + idThread::ReturnInt( animator.IsAnimPlaying(animname) ); +} + + +/* +================ +hhWeapon::Event_EjectBrass +================ +*/ +void hhWeapon::Event_EjectBrass() { + if( fireController ) { + fireController->EjectBrass(); + } +} + +/* +================ +hhWeapon::Event_EjectAltBrass +================ +*/ +void hhWeapon::Event_EjectAltBrass() { + if( altFireController ) { + altFireController->EjectBrass(); + } +} + +/* +================ +hhWeapon::Event_PlayAnimWhenReady +================ +*/ +void hhWeapon::Event_PlayAnimWhenReady( const char* animName ) { +/* + if( !IsReady() && (!dict || !dict->GetBool("pickupHasRaise") || !IsRaising()) ) { + CancelEvents( &EV_PlayAnimWhenReady ); + PostEventSec( &EV_PlayAnimWhenReady, 0.5f, animName ); + return; + } + + PlayAnim( animName, 0, &EV_Weapon_Ready ); +*/ +} + +/* +================ +hhWeapon::Event_SpawnFXAlongBone +================ +*/ +void hhWeapon::Event_SpawnFXAlongBone( idList* fxParms ) { + if ( !owner->CanShowWeaponViewmodel() || !fxParms ) { + return; + } + + HH_ASSERT( fxParms->Num() == 2 ); + + hhFxInfo fxInfo; + fxInfo.UseWeaponDepthHack( true ); + BroadcastFxInfoAlongBone( dict->GetString((*fxParms)[0].c_str()), (*fxParms)[1].c_str(), &fxInfo, NULL, false ); //rww - default to not broadcast from events +} + +/* +============================== +hhWeapon::Event_FireAltProjectiles +============================== +*/ +void hhWeapon::Event_FireAltProjectiles() { + LaunchProjectiles( altFireController ); +} + +// This is called once per frame to precompute where the weapon is pointing. This is used for determining if crosshairs should display as targetted as +// well as fire controllers to update themselves. This must be called before UpdateCrosshairs(). +void hhWeapon::PrecomputeTraceInfo() { + // This is needed for fireControllers, even if there are no crosshairs + idVec3 eyePos = owner->GetEyePosition(); + idMat3 weaponAxis = GetAxis(); + float traceDist = 1024.0f; // was CM_MAX_TRACE_DIST + + // Perform eye trace + gameLocal.clip.TracePoint( eyeTraceInfo, eyePos, eyePos + weaponAxis[0] * traceDist, + MASK_SHOT_BOUNDINGBOX | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + + // CJR: If the trace hit a portal, then force it to trace the max distance, as if it's tracing through the portal + if ( eyeTraceInfo.fraction < 1.0f ) { + idEntity *ent = gameLocal.GetTraceEntity( eyeTraceInfo ); + if ( ent->IsType( hhPortal::Type ) ) { + eyeTraceInfo.endpos = eyePos + weaponAxis[0] * traceDist; + eyeTraceInfo.fraction = 1.0f; + } + } + +} + +/* +============================== +hhWeapon::UpdateCrosshairs +============================== +*/ +void hhWeapon::UpdateCrosshairs( bool &crosshair, bool &targeting ) { + idEntity* ent = NULL; + trace_t traceInfo; + + crosshair = false; + targeting = false; + if (spawnArgs.GetBool("altModeWeapon")) { + if (WEAPON_ALTMODE) { // Moded weapon in alt-mode + if (altFireController) { + crosshair = altFireController->UsesCrosshair(); + } + } + else { // Moded weapon in normal mode + if (fireController) { + crosshair = fireController->UsesCrosshair(); + } + } + } + else { // Normal non-moded weapon + if (altFireController && altFireController->UsesCrosshair()) { + crosshair = true; + } + if (fireController && fireController->UsesCrosshair()) { + crosshair = true; + } + } + + ent = NULL; + if( crosshair ) { + traceInfo = GetEyeTraceInfo(); + if( traceInfo.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity(traceInfo); + } + targeting = ( ent && ent->fl.takedamage && !(ent->IsType( hhDoor::Type ) || ent->IsType( hhModelDoor::Type ) || ent->IsType( hhConsole::Type ) ) ); + } +} + +/* +============================== +hhWeapon::GetAmmoType +============================== +*/ +ammo_t hhWeapon::GetAmmoType( void ) const { + return (fireController) ? fireController->GetAmmoType() : 0; +} + +/* +============================== +hhWeapon::AmmoAvailable +============================== +*/ +int hhWeapon::AmmoAvailable( void ) const { + return (fireController) ? fireController->AmmoAvailable() : 0; +} + +/* +============================== +hhWeapon::AmmoInClip +============================== +*/ +int hhWeapon::AmmoInClip( void ) const { + return (fireController) ? fireController->AmmoInClip() : 0; +} + +/* +============================== +hhWeapon::ClipSize +============================== +*/ +int hhWeapon::ClipSize( void ) const { + return (fireController) ? fireController->ClipSize() : 0; +} + +/* +============================== +hhWeapon::AmmoRequired +============================== +*/ +int hhWeapon::AmmoRequired( void ) const { + return (fireController) ? fireController->AmmoRequired() : 0; +} + +/* +============================== +hhWeapon::LowAmmo +============================== +*/ +int hhWeapon::LowAmmo() { + return (fireController) ? fireController->LowAmmo() : 0; +} + +/* +============================== +hhWeapon::GetAltAmmoType +============================== +*/ +ammo_t hhWeapon::GetAltAmmoType( void ) const { + return (altFireController) ? altFireController->GetAmmoType() : 0; +} + +/* +============================== +hhWeapon::AltAmmoAvailable +============================== +*/ +int hhWeapon::AltAmmoAvailable( void ) const { + return (altFireController) ? altFireController->AmmoAvailable() : 0; +} + +/* +============================== +hhWeapon::AltAmmoInClip +============================== +*/ +int hhWeapon::AltAmmoInClip( void ) const { + return (altFireController) ? altFireController->AmmoInClip() : 0; +} + +/* +============================== +hhWeapon::AltClipSize +============================== +*/ +int hhWeapon::AltClipSize( void ) const { + return (altFireController) ? altFireController->ClipSize() : 0; +} + +/* +============================== +hhWeapon::AltAmmoRequired +============================== +*/ +int hhWeapon::AltAmmoRequired( void ) const { + return (altFireController) ? altFireController->AmmoRequired() : 0; +} + +/* +============================== +hhWeapon::LowAltAmmo +============================== +*/ +int hhWeapon::LowAltAmmo() { + return (altFireController) ? altFireController->LowAmmo() : 0; +} + +/* +============================== +hhWeapon::GetAltMode +HUMANHEAD bjk +============================== +*/ +bool hhWeapon::GetAltMode() const { + return WEAPON_ALTMODE != 0; + +} + +/* +============================== +hhWeapon::UseAmmo +============================== +*/ +void hhWeapon::UseAmmo() { + if (fireController) { + fireController->UseAmmo(); + } +} + +/* +============================== +hhWeapon::UseAltAmmo +============================== +*/ +void hhWeapon::UseAltAmmo() { + if (altFireController) { + altFireController->UseAmmo(); + } +} + +/* +============================== +hhWeapon::CheckDeferredProjectiles + +HUMANEAD: rww +============================== +*/ +void hhWeapon::CheckDeferredProjectiles(void) { + if (fireController) { + fireController->CheckDeferredProjectiles(); + } + if (altFireController) { + altFireController->CheckDeferredProjectiles(); + } +} + +/* +============================== +hhWeapon::LaunchProjectiles + +HUMANEAD: aob +============================== +*/ +void hhWeapon::LaunchProjectiles( hhWeaponFireController* controller ) { + if ( IsHidden() ) { + return; + } + + // wake up nearby monsters + if ( !spawnArgs.GetBool( "silent_fire" ) ) { + gameLocal.AlertAI( owner.GetEntity() ); + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + SetShaderParm( SHADERPARM_DIVERSITY, gameLocal.random.CRandomFloat() ); + SetShaderParm( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.realClientTime ) ); + + float low = ((float)controller->AmmoAvailable()*controller->AmmoRequired())/controller->LowAmmo(); + + if( !controller->LaunchProjectiles(pushVelocity) ) { + return; + } + + if (!gameLocal.isClient) { //HUMANHEAD rww - let everyone hear this sound, and broadcast it (so don't try to play it for client-projectile weapons) + if( controller->AmmoAvailable()*controller->AmmoRequired() <= controller->LowAmmo() && low > 1 && spawnArgs.FindKey("snd_lowammo")) { + StartSound( "snd_lowammo", SND_CHANNEL_ANY, 0, true, NULL ); + } + } + + controller->UpdateMuzzleKick(); + + // add the light for the muzzleflash + controller->MuzzleFlash(); + + //HUMANEHAD: aob + controller->WeaponFeedback(); + //HUMANHEAD END +} + +/* +=============== +hhWeapon::SetViewAnglesSensitivity + +HUMANHEAD: aob +=============== +*/ +void hhWeapon::SetViewAnglesSensitivity( float fov ) { + if( owner.IsValid() ) { + owner->SetViewAnglesSensitivity( fov / g_fov.GetFloat() ); + } +} + +/* +=============== +hhWeapon::GetDict + +HUMANHEAD: aob +=============== +*/ +const idDict* hhWeapon::GetDict( const char* objectname ) { + return gameLocal.FindEntityDefDict( objectname, false ); +} + +/* +=============== +hhWeapon::GetFireInfoDict + +HUMANHEAD: aob +=============== +*/ +const idDict* hhWeapon::GetFireInfoDict( const char* objectname ) { + const idDict* dict = GetDict( objectname ); + if( !dict ) { + return NULL; + } + + return gameLocal.FindEntityDefDict( dict->GetString("def_fireInfo"), false ); +} + +/* +=============== +hhWeapon::GetAltFireInfoDict + +HUMANHEAD: aob +=============== +*/ +const idDict* hhWeapon::GetAltFireInfoDict( const char* objectname ) { + const idDict* dict = GetDict( objectname ); + if( !dict ) { + return NULL; + } + + return gameLocal.FindEntityDefDict( dict->GetString("def_altFireInfo"), false ); +} + +void hhWeapon::Event_GetString(const char *key) { + if ( fireController ) { + idThread::ReturnString( fireController->GetString(key) ); + } +} + +void hhWeapon::Event_GetAltString(const char *key) { + if ( altFireController ) { + idThread::ReturnString( altFireController->GetString(key) ); + } +} + +/* +=============== +hhWeapon::Event_AddToAltClip +=============== +*/ +void hhWeapon::Event_AddToAltClip( int amount ) { + HH_ASSERT( altFireController ); + + altFireController->AddToClip( amount ); +} + +/* +=============== +hhWeapon::Event_AmmoInClip +=============== +*/ +void hhWeapon::Event_AltAmmoInClip( void ) { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->AmmoInClip() ); +} + +/* +=============== +hhWeapon::Event_AltAmmoAvailable +=============== +*/ +void hhWeapon::Event_AltAmmoAvailable( void ) { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->AmmoAvailable() ); +} + +/* +=============== +hhWeapon::Event_ClipSize +=============== +*/ +void hhWeapon::Event_AltClipSize( void ) { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->ClipSize() ); +} + +/* +============================== +hhWeapon::Event_GetFireDelay +============================== +*/ +void hhWeapon::Event_GetFireDelay() { + HH_ASSERT( fireController ); + + idThread::ReturnFloat( fireController->GetFireDelay() ); +} + +/* +============================== +hhWeapon::Event_GetAltFireDelay +============================== +*/ +void hhWeapon::Event_GetAltFireDelay() { + HH_ASSERT( altFireController ); + + idThread::ReturnFloat( altFireController->GetFireDelay() ); +} + +/* +============================== +hhWeapon::Event_HasAmmo +============================== +*/ +void hhWeapon::Event_HasAmmo() { + idThread::ReturnInt( fireController->HasAmmo() ); +} + +/* +============================== +hhWeapon::Event_HasAltAmmo +============================== +*/ +void hhWeapon::Event_HasAltAmmo() { + idThread::ReturnInt( altFireController->HasAmmo() ); +} + +/* +=============== +hhWeapon::Event_SetViewAnglesSensitivity + +HUMANHEAD: aob +=============== +*/ +void hhWeapon::Event_SetViewAnglesSensitivity( float fov ) { + SetViewAnglesSensitivity( fov ); +} + +/* +================ +hhWeapon::UpdateNozzleFx +================ +*/ +void hhWeapon::UpdateNozzleFx( void ) { + + if ( !nozzleFx || !g_projectileLights.GetBool()) { + return; + } + + if ( nozzleJointHandle.view == INVALID_JOINT ) { + return; + } + + // + // vent light + // + if ( nozzleGlowHandle == -1 ) { + memset(&nozzleGlow, 0, sizeof(nozzleGlow)); + if ( owner.IsValid() ) { + nozzleGlow.allowLightInViewID = owner->entityNumber+1; + } + nozzleGlow.pointLight = true; + nozzleGlow.noShadows = true; + nozzleGlow.lightRadius.x = nozzleGlowRadius; + nozzleGlow.lightRadius.y = nozzleGlowRadius; + nozzleGlow.lightRadius.z = nozzleGlowRadius; + nozzleGlow.shader = nozzleGlowShader; + GetJointWorldTransform( nozzleJointHandle, nozzleGlow.origin, nozzleGlow.axis ); + + nozzleGlow.origin += nozzleGlowOffset * nozzleGlow.axis; + nozzleGlowHandle = gameRenderWorld->AddLightDef(&nozzleGlow); + } + + GetJointWorldTransform(nozzleJointHandle, nozzleGlow.origin, nozzleGlow.axis ); + nozzleGlow.origin += nozzleGlowOffset * nozzleGlow.axis; + + nozzleGlow.shaderParms[ SHADERPARM_RED ] = nozzleGlowColor.x; + nozzleGlow.shaderParms[ SHADERPARM_GREEN ] = nozzleGlowColor.y; + nozzleGlow.shaderParms[ SHADERPARM_BLUE ] = nozzleGlowColor.z; + + // Copy parms from the weapon into this light + for( int i = 4; i < 8; i++ ) { + nozzleGlow.shaderParms[ i ] = GetRenderEntity()->shaderParms[ i ]; + } + + gameRenderWorld->UpdateLightDef(nozzleGlowHandle, &nozzleGlow); +} + +void hhWeapon::Event_HideWeapon(void) { + HideWeapon(); +} + +void hhWeapon::Event_ShowWeapon(void) { + ShowWeapon(); +} + +/* +=============== +hhWeapon::FillDebugVars +=============== +*/ +void hhWeapon::FillDebugVars(idDict *args, int page) { + idStr text; + + switch(page) { + case 1: + args->SetBool("WEAPON_LOWERWEAPON", WEAPON_LOWERWEAPON != 0); + args->SetBool("WEAPON_RAISEWEAPON", WEAPON_RAISEWEAPON != 0); + args->SetBool("WEAPON_ASIDEWEAPON", WEAPON_ASIDEWEAPON != 0); + args->SetBool("WEAPON_ATTACK", WEAPON_ATTACK != 0); + args->SetBool("WEAPON_ALTATTACK", WEAPON_ALTATTACK != 0); + args->SetBool("WEAPON_ALTMODE", WEAPON_ALTMODE != 0); + args->SetBool("WEAPON_RELOAD", WEAPON_RELOAD != 0); + break; + } +} diff --git a/neo/prey/Prey/prey_baseweapons.h b/neo/prey/Prey/prey_baseweapons.h new file mode 100644 index 00000000..c7186227 --- /dev/null +++ b/neo/prey/Prey/prey_baseweapons.h @@ -0,0 +1,365 @@ +#ifndef __HH_PREY_BASE_WEAPONS_H +#define __HH_PREY_BASE_WEAPONS_H + +class hhPlayer; + +extern const idEventDef EV_Weapon_Aside; +extern const idEventDef EV_PlayAnimWhenReady; +extern const idEventDef EV_Weapon_Feedback; +extern const idEventDef EV_PlayCycle; + +//HUMANHEAD: aob +extern const idEventDef EV_Weapon_FireAltProjectiles; +extern const idEventDef EV_Weapon_FireProjectiles; +//HUMANHEAD END + +/*********************************************************************** + + hhWeapon + + Base class for common weapon data and methods +***********************************************************************/ +class hhWeapon: public hhAnimatedEntity { + CLASS_PROTOTYPE( hhWeapon ); + + //HUMANHEAD: aob - used to allow physics object to have access to hhWeaponBases' private methods and vars + friend hhPhysics_StaticWeapon; + //HUMANHEAD END + + public: + hhWeapon(); + virtual ~hhWeapon(); + void Spawn(); + virtual void Think(); + + virtual void Clear(); + virtual void GetWeaponDef( const char *objectname, int ammoinclip ) { GetWeaponDef(objectname); } + virtual void GetWeaponDef( const char *objectname ); + virtual void SetOwner( idPlayer *_owner ); + + virtual void RestoreGUI( const char* guiKey, idUserInterface** gui ); + + virtual void EnterCinematic(); + virtual void ExitCinematic(); + void NetCatchup(); + + virtual int GetClipBits(void) const; + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); + + // GUIs + const char * Icon( void ) const; + virtual void UpdateGUI(); + + virtual void SetModel( const char *modelname ); + virtual void FreeModelDef(); + void GetJointHandle( const char* jointName, weaponJointHandle_t& handle ); + bool GetJointWorldTransform( const char* jointName, idVec3 &offset, idMat3 &axis ); + bool GetJointWorldTransform( const weaponJointHandle_t& handle, idVec3 &offset, idMat3 &axis, bool muzzleOnly = false ); //rww - added muzzleOnly parameter for mp + void SetPushVelocity( const idVec3 &pushVelocity ); + virtual void SetSkin( const idDeclSkin *skin ); + virtual void SetShaderParm( int parmnum, float value ); + + bool IsLinked( void ); + bool IsWorldModelReady( void ); + + // State control/player interface + virtual void Raise( void ); + virtual void PutAway( void ); + virtual void Reload( void ); + //HUMANHEAD: aob - put bodies here + void LowerWeapon( void ) {} + void RaiseWeapon( void ) {} + //HUMANHEAD END + virtual void HideWeapon( void ); + void ShowWeapon( void ); + void HideWorldModel( void ); + void ShowWorldModel( void ); + void OwnerDied( void ); + void BeginAttack( void ); + virtual void EndAttack( void ); + virtual bool IsReady( void ) const; + virtual bool IsReloading( void ) const; + bool IsChangable( void ) const; + virtual bool IsHolstered( void ) const; + bool IsLowered( void ) const; // nla + bool IsRising( void ) const; // nla + bool IsAside( void ) const; // nla + void PutAside( void ); // nla + void PutUpright( void ); // nla + virtual int GetAnimDoneTime( void ) const { return( animDoneTime ); } // nla + + bool ShowCrosshair( void ) const; + idEntity* DropItem( const idVec3 &velocity, int activateDelay, int removeDelay, bool died ); + bool CanDrop( void ) const; + void WeaponStolen( void ) {} + + // Script state management + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + virtual idThread *ConstructScriptObject( void ); + virtual void DeconstructScriptObject( void ); + void SetState( const char *statename, int blendFrames ); + void UpdateScript( void ); + + // Visual presentation + virtual // HUMANHEAD: made virtual + void PresentWeapon( bool showViewModel ); + int GetZoomFov(); + void GetWeaponAngleOffsets( int *average, float *scale, float *max ); + void GetWeaponTimeOffsets( float *time, float *scale ); + + //Ammo + ammo_t GetAmmoType( void ) const; + int AmmoAvailable( void ) const; + int AmmoInClip( void ) const; + void ResetAmmoClip( void ) {} + int ClipSize( void ) const; + int AmmoRequired( void ) const; + void UseAmmo(); + int LowAmmo(); + + + //HUMANHEAD: altAmmo + ammo_t GetAltAmmoType( void ) const; + int AltAmmoAvailable( void ) const; + int AltAmmoInClip( void ) const; + int AltClipSize( void ) const; + int LowAltAmmo() const; + int AltAmmoRequired( void ) const; + void UseAltAmmo(); + int LowAltAmmo(); + //HUMANHEAD END + + bool GetAltMode() const; + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + // Visual presentation + void InitWorldModel( const idDict *dict ); + void MuzzleRise( idVec3 &origin, idMat3 &axis ); + + //HUMANHEAD: aob + virtual void ParseDef( const char* objectname ); + virtual void InitScriptObject( const char* objectType ); + static idEntity* SpawnWorldModel( const char* worldModelDict, idActor* _owner ); + virtual void GetMasterDefaultPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + virtual hhPlayer* GetOwner() const { return owner.GetEntity(); } + virtual void UpdateCrosshairs( bool &crosshair, bool &targeting ); + + virtual int GetHandedness() const { return( handedness ); } + + virtual void BeginAltAttack( void ); + void EndAltAttack( void ); + + int GetStatus() { return( status ); }; + const idDict * GetDict() { return( dict ); }; + + static const idDict* GetDict( const char* objectname ); + static const idDict* GetFireInfoDict( const char* objectname ); + static const idDict* GetAltFireInfoDict( const char* objectname ); + + int GetKickEndTime() const { return kick_endtime; } + void SetKickEndTime( int endTime ) { kick_endtime = endTime; } + idVec3 GetMuzzlePosition() const { return fireController->GetMuzzlePosition(); } + idVec3 GetAltMuzzlePosition() const { return altFireController->GetMuzzlePosition(); } + + void SnapDown(); + void SnapUp(); + + void PrecomputeTraceInfo(); + const trace_t& GetEyeTraceInfo() const { return eyeTraceInfo; } + + void SetViewAnglesSensitivity( float fov ); + + void UpdateNozzleFx( void ); + //HUMANEHAD END + + void CheckDeferredProjectiles(void); //HUMANHEAD rww + + virtual void FillDebugVars( idDict *args, int page ); //HUMANHEAD bjk + + protected: + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + + void LaunchProjectiles( hhWeaponFireController* controller ); + + //needed for overridding + //FIXME: would like to make these templates just in case we change types. + ID_INLINE virtual hhWeaponFireController* CreateFireController(); + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); + //HUMANHEAD END + + protected: + //HUMANHEAD: aob + void Event_PlayAnimWhenReady( const char* animName ); + void Event_Raise(); // nla + void Event_Weapon_Aside(); + void Event_Weapon_PuttingAside(); + void Event_Weapon_Uprighting(); + + //Called from frame command + void Event_SpawnFXAlongBone( idList* fxParms ); + + void Event_WeaponOutOfAltAmmo(); + void Event_AddToAltClip( int amount ); + void Event_AltAmmoInClip(); + void Event_AltAmmoAvailable(); + void Event_AltClipSize(); + void Event_HasAltAmmo(); + + void Event_HasAmmo(); + + virtual void Event_FireAltProjectiles(); + virtual void Event_FireProjectiles(); + + void Event_EjectAltBrass( void ); + + void Event_IsAnimPlaying( const char *animname ); // CJR + + void Event_GetFireDelay(); + void Event_GetAltFireDelay(); + void Event_GetSpread(); + void Event_GetAltSpread(); + void Event_GetString(const char *key); + void Event_GetAltString(const char *key); + + void Event_SetViewAnglesSensitivity( float fov); + void Event_GetOwner( void ); + void Event_UseAmmo( int amount ); + void Event_UseAltAmmo( int amount ); + //HUMANHEAD END + + //idWeapon events + // script events + void Event_WeaponState( const char *statename, int blendFrames ); + void Event_SetWeaponStatus( float newStatus ); + void Event_WeaponReady( void ); + void Event_WeaponOutOfAmmo( void ); + void Event_WeaponReloading( void ); + void Event_WeaponHolstered( void ); + void Event_WeaponRising( void ); + void Event_WeaponLowering( void ); + void Event_AddToClip( int amount ); + void Event_AmmoInClip( void ); + void Event_AmmoAvailable( void ); + void Event_ClipSize( void ); + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + void Event_AnimDone( int channel, int blendFrames ); + void Event_WaitFrame( void ); + void Event_Next( void ); + void Event_SetSkin( const char *skinname ); + void Event_Flashlight( int enable ); + void Event_EjectBrass( void ); + void Event_HideWeapon( void ); + void Event_ShowWeapon( void ); + + protected: + hhWeaponFireController* fireController; + hhWeaponFireController* altFireController; + trace_t eyeTraceInfo; + + idVec3 cameraShakeOffset; + hhPhysics_StaticWeapon physicsObj; + + // 0 - no hands, 1 - right, 2 - left, 3 - both + int handedness; // nla - For determining which hands can be up with which weapons + + idScriptBool WEAPON_ALTATTACK; + idScriptBool WEAPON_ASIDEWEAPON; // nla + idScriptBool WEAPON_ALTMODE; // for moded weapons like rifle, whether in alt mode + + //idWeapon vars + // script control + idScriptBool WEAPON_ATTACK; + idScriptBool WEAPON_RELOAD; + idScriptBool WEAPON_RAISEWEAPON; + idScriptBool WEAPON_LOWERWEAPON; + idScriptFloat WEAPON_NEXTATTACK; //rww + weaponStatus_t status; + idThread * thread; + idStr state; + idStr idealState; + int animBlendFrames; + int animDoneTime; + + //HUMANHEAD: aob - made hhPlayer + idEntityPtr owner; + idEntityPtr worldModel; + + // weapon kick + int kick_endtime; + + idVec3 pushVelocity; + + // weapon definition + const idDeclEntityDef * weaponDef; + const idDict * dict; + + idStr icon; + + bool lightOn; + + // zoom + int zoomFov; // variable zoom fov per weapon + + // weighting for viewmodel angles + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale; + float weaponAngleOffsetMax; + float weaponOffsetTime; + float weaponOffsetScale; + + // Nozzle FX + bool nozzleFx; + weaponJointHandle_t nozzleJointHandle; + renderLight_t nozzleGlow; // nozzle light + int nozzleGlowHandle; // handle for nozzle light + idVec3 nozzleGlowColor; // color of the nozzle glow + const idMaterial * nozzleGlowShader; // shader for glow light + float nozzleGlowRadius; // radius of glow light + idVec3 nozzleGlowOffset; // offset from bound bone + +}; + +/* +================ +hhWeapon::IsLinked +================ +*/ +ID_INLINE bool hhWeapon::IsLinked( void ) { + return scriptObject.HasObject(); +} + +/* +================ +hhWeapon::IsWorldModelReady +================ +*/ +ID_INLINE bool hhWeapon::IsWorldModelReady( void ) { + return worldModel.IsValid(); +} + +/* +================ +hhWeapon::CreateFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeapon::CreateFireController() { + return new hhWeaponFireController; +} + +/* +================ +hhWeapon::CreateAltFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeapon::CreateAltFireController() { + return CreateFireController(); +} + +#endif diff --git a/neo/prey/Prey/prey_beam.cpp b/neo/prey/Prey/prey_beam.cpp new file mode 100644 index 00000000..196af4e3 --- /dev/null +++ b/neo/prey/Prey/prey_beam.cpp @@ -0,0 +1,1150 @@ +//************************************************************************** +//** +//** PREY_BEAM.CPP +//** +//** Code for Prey-specific beams +//** +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +// Game code includes +#include "prey_local.h" + +#define DEBUG_BEAMS 0 + +static idVec3 CurveExtractSpline(idVec3 *cPoints, int cPointCount, float alpha, bool bThruCtrlPnts); + +const idEventDef EV_ToggleBeamLength("", "d"); +const idEventDef EV_SetBeamPhaseScale( "setBeamPhaseScale", "f" ); // bg +const idEventDef EV_SetBeamOffsetScale( "setBeamOffsetScale", "f" ); // bg + +CLASS_DECLARATION(idEntity, hhBeamSystem) + EVENT( EV_Activate, hhBeamSystem::Event_Activate ) + EVENT( EV_SetBeamPhaseScale, hhBeamSystem::Event_SetBeamPhaseScale) // bg + EVENT( EV_SetBeamOffsetScale, hhBeamSystem::Event_SetBeamOffsetScale) // bg +END_CLASS + + +hhBeamSystem::hhBeamSystem() { + beamList = NULL; + offsetScale = 1.0f; + phaseScale = 1.0f; + bActive = false; + + targetLocation = vec3_origin; + + decl = NULL; +} + +hhBeamSystem::~hhBeamSystem() { + if(renderEntity.beamNodes) { + Mem_Free( renderEntity.beamNodes ); + renderEntity.beamNodes = NULL; + } + + if(beamList) { + delete [] beamList; + beamList = NULL; + } +} + +void hhBeamSystem::Spawn(void) { + beamList = NULL; + + if (!gameLocal.isClient || fl.clientEntity) { //rww - for client in mp this gets done after the first snapshot is read... + InitSystem( spawnArgs.GetString("model") ); + } + + beamLength = spawnArgs.GetFloat( "lengthBeam" ); + bRigidBeamLength = spawnArgs.GetBool( "rigidBeamLength" ); + + targetEntity = NULL; + targetEntityId = 0; + targetEntityOffset.Zero(); + + SetArcVector( vec3_origin ); + + bActive = spawnArgs.GetBool( "start_off" ); + Activate( !bActive ); +} + +void hhBeamSystem::Activate( const bool bActivate ) { + if( bActive != bActivate ) { + bActive = bActivate; + + if( bActive ) { + BecomeActive( TH_UPDATEPARTICLES ); + Show(); + }else { + BecomeInactive( TH_UPDATEPARTICLES ); + Hide(); + } + } +} + +hhBeamSystem* hhBeamSystem::SpawnBeam( const idVec3& start, const char* modelName, const idMat3& axis, bool pureLocal ) { + idDict Args; + hhBeamSystem* beamSystem = NULL; + idStr modelString; + +#if !GOLD + //rww - checking for creation of beams at nan location + assert(!FLOAT_IS_NAN(start.x)); + assert(!FLOAT_IS_NAN(start.y)); + assert(!FLOAT_IS_NAN(start.z)); + assert(!FLOAT_IS_NAN(axis[0].x)); + assert(!FLOAT_IS_NAN(axis[0].y)); + assert(!FLOAT_IS_NAN(axis[0].z)); + assert(!FLOAT_IS_NAN(axis[1].x)); + assert(!FLOAT_IS_NAN(axis[1].y)); + assert(!FLOAT_IS_NAN(axis[1].z)); + assert(!FLOAT_IS_NAN(axis[2].x)); + assert(!FLOAT_IS_NAN(axis[2].y)); + assert(!FLOAT_IS_NAN(axis[2].z)); +#endif + + if ( !modelName || !modelName[0] ) { + return NULL; + } + + // Ensure that the modelname ends with a valid .beam extention + modelString = modelName; + modelString.DefaultFileExtension( ".beam" ); + + Args.Set( "spawnclass", "hhBeamSystem" ); + Args.Set( "model", modelString.c_str() ); + Args.SetVector( "origin", start ); + Args.SetMatrix( "rotation", axis ); + + HH_ASSERT(!gameLocal.isClient || pureLocal); + + beamSystem = (hhBeamSystem *)gameLocal.SpawnEntityTypeClient( hhBeamSystem::Type, &Args ); + if (beamSystem) { //make sure it's sync'd + beamSystem->fl.networkSync = !pureLocal; + } + HH_ASSERT( beamSystem ); + + beamSystem->SetTargetEntity( NULL ); + beamSystem->SetTargetLocation( start ); // Necessary in case the beam starts out hidden + beamSystem->beamAxis = mat3_identity; + + return beamSystem; +} + +//========================================================================== +// +// hhBeamSystem::InitSystem +// +// Reads beam system information from the entity definition +// +// Commands: +// beamNum - total number of beams in the system +// beamNodes - total number of nodes per beam +// +// Per beam (end number specifies the beam): +// +// beamThickness0 - Thickness +// beamTaperEndPoints0 - If the beam should have tapered end points +// beamShader0 - Shader name +// beamSpline0 - if this beam system uses splines +//========================================================================== + +void hhBeamSystem::InitSystem( const char* modelName ) { + int i; + + HH_ASSERT( modelName ); + + if(renderEntity.beamNodes) { // Delete the previous version before initializing + Mem_Free(renderEntity.beamNodes); + renderEntity.beamNodes = NULL; + } + + // Initialize beam data from the beam file + declName = modelName; + decl = (const hhDeclBeam*)declManager->FindType(DECL_BEAM, modelName, false); + renderEntity.declBeam = decl; + + // Get new model loaded + if( !renderEntity.hModel || idStr::Icmp( renderEntity.hModel->Name(), modelName ) ) { + renderEntity.hModel = renderModelManager->FindModel( modelName ); + } + + // Allocate the beams + if( beamList ) { + delete[] beamList; + } + beamList = new hhBeam[decl->numBeams]; + + // Allocate the number of beam infos (used for rendering) + renderEntity.beamNodes = (hhBeamNodes_t *)Mem_ClearedAlloc(sizeof(hhBeamNodes_t) * decl->numBeams); + + beamAxis = GetPhysics()->GetAxis(); + + // Initialize each beam with specific info + for(i = 0; i < decl->numBeams; i++) { + beamList[i].Init( this, &renderEntity.beamNodes[i] ); + } + + random.SetSeed( gameLocal.time ); + beamTime = gameLocal.time + random.RandomInt( 5000 ); // Don't let any beams stay in sync. +} + +//========================================================================== +// +// hhBeamSystem::SetTargetLocation +// +//========================================================================== + +void hhBeamSystem::SetTargetLocation(idVec3 newLoc) { + targetLocation = newLoc; + + beamLength = (targetLocation - GetPhysics()->GetOrigin()).Length(); +} + +//========================================================================== +// +// hhBeamSystem::SetTargetEntity +// +// Sets the entity to target from this beam. If joint is INVALID_JOINT, +// then the origin of the entity will be used. +// +// Passing in a NULL entity will clear the target entity, and set the targetLocation to offset +// +// NOTE: The offset passed in should be in world coordinates relative to the model. +// NOTE: The offset can be used as an offset from the joint as well. +//========================================================================== + +void hhBeamSystem::SetTargetEntity( idEntity *ent, int traceId, idVec3 &offset ) { + idVec3 origin; + idMat3 axis; + + targetEntity = ent; + + if ( ent == NULL ) { + targetEntityId = 0; + targetEntityOffset.Zero(); + SetTargetLocation( offset ); + return; + } + + targetEntityId = traceId; + targetEntityOffset = offset; + + // Update the targetLocation + GetTargetLocation(); + + // Calculate the beam length from the new target location + beamLength = (targetLocation - GetPhysics()->GetOrigin()).Length(); +} + +//========================================================================== +// +// hhBeamSystem::SetTargetEntity +// +// Bone name version +//========================================================================== + +void hhBeamSystem::SetTargetEntity( idEntity *ent, const char *boneName, idVec3 &offset ) { + idVec3 origin; + idMat3 axis; + + targetEntity = ent; + + if ( ent == NULL || boneName == NULL ) { + targetEntityId = 0; + targetEntityOffset.Zero(); + SetTargetLocation( offset ); + return; + } + + targetEntityId = ent->GetAnimator()->GetJointHandle( boneName ); + targetEntityOffset = offset; + + // Update the targetLocation + GetTargetLocation(); + + // Calculate the beam length from the new target location + beamLength = (targetLocation - GetPhysics()->GetOrigin()).Length(); +} + +//========================================================================== +// +// hhBeamSystem::GetTargetLocation +// +//========================================================================== + +idVec3 hhBeamSystem::GetTargetLocation( void ) { + idVec3 origin; + idMat3 axis; + idVec3 boneOrigin; + idMat3 boneAxis; + jointHandle_t joint; + idAFEntity_Base *af = NULL; + + if ( targetEntity.IsValid() ) { // Has a target entity, so compute the offset + if ( targetEntityId > 0 ) { // This is a specific joint: + joint = (jointHandle_t)targetEntityId; + } else { // This is a collision handle + joint = CLIPMODEL_ID_TO_JOINT_HANDLE( targetEntityId ); + } + + if ( targetEntity->IsType( idAFEntity_Base::Type ) ) { + af = static_cast( targetEntity.GetEntity() ); + } + + axis = targetEntity->GetAxis(); + origin = targetEntity->GetOrigin(); + + // Calculate the targetLocation, based upon the type of entity + if ( af && af->IsActiveAF() ) { // AF, use the clip model id for the origin + int body = af->BodyForClipModelId( targetEntityId ); + origin = af->GetPhysics()->GetOrigin( body ); + targetLocation = origin + axis * targetEntityOffset; + //rww - added check for targetEntity->GetAnimator() not being null, in case targetEntity is not animated + } else if ( joint != INVALID_JOINT && targetEntity->GetAnimator() && targetEntity->GetAnimator()->GetJointTransform( joint, gameLocal.time, boneOrigin, boneAxis ) ) { // Joint-based + targetLocation = origin + axis * boneOrigin; + } else { // Default - use origin of the entity + targetLocation = origin + axis * targetEntityOffset; + } + } + + return targetLocation; +} + +//========================================================================== +// +// hhBeamSystem::SetAxis +// +//========================================================================== +void hhBeamSystem::SetAxis( const idMat3 &axis ) { + if (!GetPhysics()->GetAxis().Compare(axis)) { + idEntity::SetAxis(axis); + } +} + +//========================================================================== +// +// hhBeamSystem::SetBeamLength +// +//========================================================================== +void hhBeamSystem::SetBeamLength( const float length ) { + beamLength = length; + + if( bRigidBeamLength && beamLength > VECTOR_EPSILON ) { + SetTargetLocation( GetPhysics()->GetOrigin() + GetPhysics()->GetAxis()[0] * beamLength ); + } +} + +/* +================= +hhBeamSystem::WriteToSnapshot +rww - write applicable beam values to snapshot +================= +*/ +void hhBeamSystem::WriteToSnapshot( idBitMsgDelta &msg ) const { +#if !GOLD + gameLocal.Warning("Beam %i being sync'd by server!\n", entityNumber); +#endif + GetPhysics()->WriteToSnapshot( msg ); + + msg.WriteFloat(beamTime); + + //handle this. + //hhBeam *beamList; + + //this is the common method, but doing WriteDirs might be more efficient? + idCQuat quat = beamAxis.ToCQuat(); + msg.WriteFloat(quat.x); + msg.WriteFloat(quat.y); + msg.WriteFloat(quat.z); + + msg.WriteFloat(arcVector[0]); + msg.WriteFloat(arcVector[1]); + msg.WriteFloat(arcVector[2]); + + msg.WriteFloat(targetLocation[0]); + msg.WriteFloat(targetLocation[1]); + msg.WriteFloat(targetLocation[2]); + + if (targetEntity.IsValid()) { + msg.WriteBits(1, 1); + msg.WriteBits(targetEntity->entityNumber, GENTITYNUM_BITS); + } + else { + msg.WriteBits(0, 1); + } + + msg.WriteFloat(targetEntityOffset[0]); + msg.WriteFloat(targetEntityOffset[1]); + msg.WriteFloat(targetEntityOffset[2]); + + //do i care about this? + //int targetEntityId; + + msg.WriteFloat(beamLength); + msg.WriteBits(bRigidBeamLength, 1); + + msg.WriteFloat(phaseScale); + msg.WriteFloat(offsetScale); + + msg.WriteBits(bActive, 1); + + //rwwFIXME this is horrible. oh so very horrible. all those calls to SpawnBeam in code need to be removed, + //and they must all use seperate entity defs, or there is no alternative to sending a real string. =| + msg.WriteString(spawnArgs.GetString("model")); +} + +/* +================= +hhBeamSystem::ReadFromSnapshot +rww - read applicable beam values from snapshot +================= +*/ +void hhBeamSystem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot( msg ); + + beamTime = msg.ReadFloat(); + + idCQuat quat; + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + beamAxis = quat.ToMat3(); + + arcVector[0] = msg.ReadFloat(); + arcVector[1] = msg.ReadFloat(); + arcVector[2] = msg.ReadFloat(); + + targetLocation[0] = msg.ReadFloat(); + targetLocation[1] = msg.ReadFloat(); + targetLocation[2] = msg.ReadFloat(); + + int hasEnt = msg.ReadBits(1); + if (hasEnt) { + int entNum = msg.ReadBits(GENTITYNUM_BITS); + targetEntity = gameLocal.entities[entNum]; + } + else { + targetEntity = NULL; + } + + targetEntityOffset[0] = msg.ReadFloat(); + targetEntityOffset[1] = msg.ReadFloat(); + targetEntityOffset[2] = msg.ReadFloat(); + + beamLength = msg.ReadFloat(); + bRigidBeamLength = !!msg.ReadBits(1); + + phaseScale = msg.ReadFloat(); + offsetScale = msg.ReadFloat(); + + bool active = !!msg.ReadBits(1); + if (active != bActive) { + Activate(active); + } + + char modelName[128]; + msg.ReadString(modelName, 128); + if (!beamList) { + //then init the system. + InitSystem(modelName); + } +} + +/* +================= +hhBeamSystem::ClientPredictionThink +rww - minimal think on client +================= +*/ +void hhBeamSystem::ClientPredictionThink( void ) { + if (fl.clientEntity && snapshotOwner.IsValid()) { + if (!bActive || !gameLocal.EntInClientSnapshot(snapshotOwner->entityNumber)) { //if the snapshot entity i'm associated with is not in the snapshot, i hide + Hide(); + } + else { + Show(); + } + } + + if (gameLocal.isNewFrame) { + Think(); + } +} + +//========================================================================== +// +// hhBeamSystem::Think +// +// NOTE: Beams will not think AT ALL if they are hidden or not visible +//========================================================================== + +void hhBeamSystem::Think( void ) { + + RunPhysics(); + + if (thinkFlags & TH_UPDATEPARTICLES) { + + if( targets.Num() > 0 ) { + SetTargetEntity( targets[0].GetEntity(), NULL ); + } + + // Update the beamAxis to correctly reflect the target + if( !bRigidBeamLength ) { + idVec3 vec = (GetTargetLocation() - GetPhysics()->GetOrigin()); + beamLength = vec.Normalize(); + + if ( beamLength <= 0 ) { // Ignore beams with no length + return; + } + + if( GetBindMaster() && fl.bindOrientated ) { + beamAxis = GetAxis(); + } else { + beamAxis = vec.ToMat3(); + + if ( targets.Num() > 0 ) { + SetAxis( beamAxis ); // Target beams should update renderEntity information + } else { + GetPhysics()->SetAxis( beamAxis ); // Normal beams don't need to update render entity information + } + } + } else { + SetTargetLocation( GetPhysics()->GetOrigin() + GetPhysics()->GetAxis()[0] * beamLength ); + beamAxis = GetPhysics()->GetAxis(); + } + + assert(decl); + for( int i = 0; i < decl->numBeams; i++) { + ExecuteBeam( i, &beamList[i] ); + beamList[i].TransformNodes(); + } + } + + Present(); +} + +//========================================================================== +// +// hhBeamSystem::Event_Activate +// +// Triggering a beam will toggle its visibility +//========================================================================== + +void hhBeamSystem::Event_Activate( idEntity *activator ) { + Activate( !IsActivated() ); +} + +//========================================================================== +// +// hhBeamSystem::Event_SetBeamPhaseScale +// +//========================================================================== + +void hhBeamSystem::Event_SetBeamPhaseScale( float scale ) { + SetBeamPhaseScale( scale ); +} + +//========================================================================== +// +// hhBeamSystem::Event_SetBeamOffsetScale +// +//========================================================================== + +void hhBeamSystem::Event_SetBeamOffsetScale( float scale ) { + SetBeamOffsetScale( scale ); +} + +//========================================================================== +// +// hhBeamSystem::UpdateModel +// +//========================================================================== + +void hhBeamSystem::UpdateModel( void ) { + + if( renderEntity.hModel && !this->fl.hidden ) { // Only calculate the bounds if the model is valid and the model is visible + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + renderEntity.bounds.AddPoint(idVec3(-16,-16,-16)); + renderEntity.bounds.AddPoint(idVec3( 16, 16, 16)); + } + + idEntity::UpdateModel(); +} + + +// BEAM CODE --------------------------------------------------------------------- + +//========================================================================== +// +// hhBeam::hhBeam +// +//========================================================================== + +hhBeam::hhBeam() { + nodeList = NULL; +} + +//========================================================================== +// +// hhBeam::~hhBeam +// +//========================================================================== + +hhBeam::~hhBeam() { + nodeList = NULL; +} + +//========================================================================== +// +// hhBeam::Init +// +//========================================================================== + +void hhBeam::Init( hhBeamSystem *newSystem, hhBeamNodes_t *newInfo ) { + int i; + + system = newSystem; + nodeList = newInfo->nodes; + + for(i = 0; i < MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS; i++) { + splineList[i] = idVec3(0, 0, 0); + } +} + +//========================================================================== +// +// hhBeam::VerifyNodeIndex +// +//========================================================================== + +void hhBeam::VerifyNodeIndex(int index, const char *functionName) { +#if DEBUG_BEAMS + if(index < 0 || index >= system->decl->numNodes) { + gameLocal.Error("%s: %d\n", functionName, index); + } +#endif +} + +//========================================================================== +// +// hhBeam::VerifySplineIndex +// +//========================================================================== + +void hhBeam::VerifySplineIndex(int index, const char *functionName) { +#if DEBUG_BEAMS + if(index < 0 || index >= MAX_BEAM_SPLINE_CONTROLS) { + gameLocal.Error("%s: %d\n", functionName, index); + } +#endif +} + +//========================================================================== +// +// hhBeam::NodeGet +// +// Returns the node location in world space +//========================================================================== + +idVec3 hhBeam::NodeGet(int index) { +#if DEBUG_BEAMS + VerifyNodeIndex(index, "hhBeam::NodeGet"); +#endif + + return( nodeList[index] ); +} + +//========================================================================== +// +// hhBeam::NodeSet +// +//========================================================================== + +void hhBeam::NodeSet(int index, idVec3 value) { +#if DEBUG_BEAMS + VerifyNodeIndex(index, "hhBeam::NodeSet"); +#endif + + nodeList[index] = value; +} + +//========================================================================== +// +// hhBeam::SplineGet +// +//========================================================================== + +idVec3 hhBeam::SplineGet(int index) { +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineSet"); +#endif + + return(splineList[index]); +} + +//========================================================================== +// +// hhBeam::SplineSet +// +//========================================================================== + +void hhBeam::SplineSet(int index, idVec3 value) { +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineSet"); +#endif + + splineList[index] = value; +} + +//========================================================================== +// +// hhBeam::SplineLinear +// +//========================================================================== + +void hhBeam::SplineLinear(idVec3 start, idVec3 end) { + int i; + idVec3 loc; + idVec3 delta; + + loc = start; + delta = (end - start) * BEAM_SPLINE_CONTROL_STEP; + + for(i = 0; i < MAX_BEAM_SPLINE_CONTROLS; i++) { + splineList[i] = loc; + loc += delta; + } +} + +//========================================================================== +// +// hhBeam::SplineLinearToTarget +// +//========================================================================== + +void hhBeam::SplineLinearToTarget( void ) { + SplineLinear( system->GetRenderEntity()->origin, system->GetTargetLocation() ); +} + +//========================================================================== +// +// hhBeam::SplineArc +// +//========================================================================== + +void hhBeam::SplineArc( idVec3 start, idVec3 end, idVec3 startVec ) { + int i; + idMat3 axis; + idVec3 loc; + idVec3 delta; + + idVec3 temp = startVec; + temp.Normalize(); + + // Calculate the translation matrix + axis[0] = ( end - start ); + float dist = axis[0].Normalize(); + axis[2] = temp.Cross( axis[0] ); + axis[1] = axis[2].Cross( axis[0] ); + + float dp = temp * axis[0]; + + loc = start; + delta = (end - start) * BEAM_SPLINE_CONTROL_STEP; + + float x = 0; + for( i = 0; i < MAX_BEAM_SPLINE_CONTROLS; i++, x += BEAM_SPLINE_CONTROL_STEP ) { + float temp = -dist * dp * x * ( 1 - x ); + splineList[i] = loc + axis[1] * temp; + loc += delta; + } +} + +//========================================================================== +// +// hhBeam::SplineArcToTarget +// +//========================================================================== + +void hhBeam::SplineArcToTarget( void ) { + SplineArc( system->GetRenderEntity()->origin, system->GetTargetLocation(), system->GetArcVector() ); +} + +//========================================================================== +// +// hhBeam::SplineAddSin +// +//========================================================================== + +void hhBeam::SplineAddSin(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ) { + idVec3 sinOffset; + +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineAddSin"); +#endif + + sinOffset.x = offsetX * idMath::Sin(phaseX); + sinOffset.y = offsetY * idMath::Sin(phaseY); + sinOffset.z = offsetZ * idMath::Sin(phaseZ); + + splineList[index] += sinOffset * system->GetBeamAxis(); +} + +//========================================================================== +// +// hhBeam::SplineAddSinTime +// +// Multiplies the sin value by the current game time +//========================================================================== + +void hhBeam::SplineAddSinTime(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ) { + float t; + + t = (gameLocal.time - system->GetBeamTime()) * 0.01f; + SplineAddSin( index, phaseX * t, phaseY * t, phaseZ * t, offsetX, offsetY, offsetZ ); +} + +//========================================================================== +// +// hhBeam::SplineAddSinTimeScaled +// +// Multiplies the sin value by the current game time +//========================================================================== + +void hhBeam::SplineAddSinTimeScaled(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ) { + float t; + float offsetScale = system->GetBeamOffsetScale(); + + t = system->GetBeamPhaseScale() * (gameLocal.time - system->GetBeamTime()) * 0.01f; + SplineAddSin( index, phaseX * t, phaseY * t, phaseZ * t, + offsetX * offsetScale, offsetY * offsetScale, offsetZ * offsetScale ); +} + +//========================================================================== +// +// hhBeam::SplineAdd +// +//========================================================================== + +void hhBeam::SplineAdd(int index, idVec3 offset) { +#if DEBUG_BEAMS + VerifySplineIndex(index, "hhBeam::SplineAdd"); +#endif + + float offsetScale = system->GetBeamOffsetScale(); + splineList[index] += offset * offsetScale; +} + +//========================================================================== +// +// hhBeam::ConvertSplineToNodes +// +//========================================================================== + +void hhBeam::ConvertSplineToNodes(void) { + int i; + + // Copy the last spline point into the extra "slop" points + // The extra points are necessary to calculate the final spline segment + for(i = 0; i < EXTRA_SPLINE_CONTROLS; i++) { + splineList[MAX_BEAM_SPLINE_CONTROLS + i] = splineList[MAX_BEAM_SPLINE_CONTROLS - 1]; + } + + // Construct the beam nodes from the spline points + float inc = (float)(MAX_BEAM_SPLINE_CONTROLS) / system->decl->numNodes; + float alpha = 0; + + for(i = 0; i < system->decl->numNodes; i++) { + nodeList[i] = CurveExtractSpline(splineList, MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS, alpha, true); + alpha += inc; + } +} + +//========================================================================== +// +// hhBeam::NodeLinear +// +//========================================================================== + +void hhBeam::NodeLinear(idVec3 start, idVec3 end) { + int i; + + idVec3 sLoc = start; + idVec3 sDelta = (end - start) / (system->decl->numNodes - 1); + + for(i = 0; i < system->decl->numNodes; i++) { + nodeList[i] = sLoc; + sLoc += sDelta; + } +} + +//========================================================================== +// +// hhBeam::NodeLinearToTarget +// +//========================================================================== + +void hhBeam::NodeLinearToTarget( void ) { + NodeLinear( system->GetRenderEntity()->origin, system->GetTargetLocation() ); +} + +//========================================================================== +// +// hhBeam::NodeAdd +// +//========================================================================== + +void hhBeam::NodeAdd(int index, idVec3 offset) { +#if DEBUG_BEAMS + VerifyNodeIndex(index,"hhBeam::NodeAdd"); +#endif + + nodeList[index] += offset; +} + +//========================================================================== +// +// hhBeam::NodeElectric +// +//========================================================================== + +void hhBeam::NodeElectric(float x, float y, float z, bool bNotEnds) { + int i; + int start = 0; + int end = system->decl->numNodes; + + if(bNotEnds) { + start = 1; + end = system->decl->numNodes - 1; + } + + for(i = start; i < end; i++) { + nodeList[i].x += system->random.CRandomFloat() * x; + nodeList[i].y += system->random.CRandomFloat() * y; + nodeList[i].z += system->random.CRandomFloat() * z; + } +} + +//========================================================================== +// +// hhBeam::TransformNodes +// +// Transform the worldspace nodes into model local space +//========================================================================== + +void hhBeam::TransformNodes(void) { + idVec3 origin = system->GetRenderEntity()->origin; + idMat3 axis = system->GetRenderEntity()->axis.Transpose(); + for(int i = 0; i < system->decl->numNodes; i++) { + nodeList[i] = ( nodeList[i] - origin ) * axis; + } +} + +//========================================================================== +// +// hhBeam::GetBounds +// +// Get local bounds of beam +//========================================================================== +idBounds hhBeam::GetBounds() { + idBounds bounds; + + if( !nodeList ) { + return bounds_zero; + } + + for( int ix = 0; ix < system->decl->numNodes; ++ix ) { + bounds.AddPoint( nodeList[ix] ); + } + + return bounds; +} + +//================ +//hhBeam::Save +//================ +void hhBeam::Save( idSaveGame *savefile ) const { + for( int i = 0; i < MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS; i++ ) { + savefile->WriteVec3( splineList[i] ); + } +} + +//================ +//hhBeam::Restore +//================ +void hhBeam::Restore( idRestoreGame *savefile, hhBeamSystem *newSystem, hhBeamNodes_t *newInfo) { + system = newSystem; + nodeList = newInfo->nodes; + + for( int i = 0; i < MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS; i++ ) { + savefile->ReadVec3( splineList[i] ); + } +} + +//============================================================================= +// +// CurveExtractSpline +// +//============================================================================= + +static idVec3 CurveExtractSpline(idVec3 *cPoints, int cPointCount, float alpha, bool bThruCtrlPnts ) { + float t, t2, t3, w1, w2, w3, w4; + int p1, p2, p3, p4, intAlpha; + idVec3 result; + + intAlpha = alpha; + p2 = intAlpha; + p1 = (p2 == 0) ? p2 : p2-1; + p3 = p2+1; + p4 = (p3 == cPointCount-1) ? p3 : p3+1; + t = alpha-intAlpha; + t2 = t * t; + t3 = t2 * t; + + if(!bThruCtrlPnts) { + w1 = (1-t) * (1-t) * (1-t); + w2 = 3.0f * t3 - 6.0f * t2 + 4; + w3 = -3.0f * t3 + 3.0f * t2 + 3.0f * t + 1; + w4 = t3; + return + ( w1 * cPoints[p1] + + w2 * cPoints[p2] + + w3 * cPoints[p3] + + w4 * cPoints[p4]) * (1.0f/6.0f); + } + + // Uses Catmull-Rom to pass thru the control points. + + if((cPoints[p3] - cPoints[p2]).Length() <= 4.0f) // was 16 + { // If points are close enough, just linearly interpolate + result = cPoints[p2] + t * (cPoints[p3] - cPoints[p2]); + } + else { + result = 0.5f * ((-cPoints[p1] + 3 * cPoints[p2] - 3 * cPoints[p3] + cPoints[p4]) * t3 + + (2 * cPoints[p1] - 5 * cPoints[p2]+ 4 * cPoints[p3] - cPoints[p4]) * t2 + + (-cPoints[p1] + cPoints[p3]) * t + 2 * cPoints[p2]); + } + + return(result); +} + +//============================================================================= +// +// hhBeamSystem::ExecuteBeam +// +// Applies the commands to the specified beam +//============================================================================= + +void hhBeamSystem::ExecuteBeam( int index, hhBeam *beam ) { + int i; + const beamCmd_t *cmd; + + for(i = 0; i < decl->cmds[index].Num(); i++) { + cmd = &decl->cmds[index][i]; + + switch( cmd->type ) { + case BEAMCMD_SplineLinearToTarget: + beam->SplineLinearToTarget(); + break; + case BEAMCMD_SplineArcToTarget: + beam->SplineArcToTarget(); + break; + case BEAMCMD_SplineAdd: + beam->SplineAdd( cmd->index, cmd->offset ); + break; + case BEAMCMD_SplineAddSin: + beam->SplineAddSin( cmd->index, cmd->phase.x, cmd->phase.y, cmd->phase.z, cmd->offset.x, cmd->offset.y, cmd->offset.z ); + break; + case BEAMCMD_SplineAddSinTim: + beam->SplineAddSinTime( cmd->index, cmd->phase.x, cmd->phase.y, cmd->phase.z, cmd->offset.x, cmd->offset.y, cmd->offset.z ); + break; + case BEAMCMD_SplineAddSinTimeScaled: + beam->SplineAddSinTimeScaled( cmd->index, cmd->phase.x, cmd->phase.y, cmd->phase.z, cmd->offset.x, cmd->offset.y, cmd->offset.z ); + break; + case BEAMCMD_ConvertSplineToNodes: + beam->ConvertSplineToNodes(); + break; + case BEAMCMD_NodeLinearToTarget: + beam->NodeLinearToTarget(); + break; + case BEAMCMD_NodeElectric: + beam->NodeElectric( cmd->offset.x, cmd->offset.y, cmd->offset.z, true ); + break; + } + } +} + +//================ +//hhBeamSystem::Save +//================ +void hhBeamSystem::Save( idSaveGame *savefile ) const { + savefile->WriteString( declName ); + savefile->WriteInt( random.GetSeed() ); + savefile->WriteFloat( beamTime ); + + for( int i = 0; i < decl->numBeams; i++) { + beamList[i].Save( savefile ); + } + + savefile->WriteMat3( beamAxis ); + savefile->WriteVec3( arcVector ); + savefile->WriteVec3( targetLocation ); + targetEntity.Save( savefile ); + savefile->WriteVec3( targetEntityOffset ); + savefile->WriteInt( targetEntityId ); + savefile->WriteFloat( beamLength ); + savefile->WriteBool( bRigidBeamLength ); + savefile->WriteFloat( phaseScale ); + savefile->WriteFloat( offsetScale ); + savefile->WriteBool( bActive ); +} + +//================ +//hhBeamSystem::Restore +//================ +void hhBeamSystem::Restore( idRestoreGame *savefile ) { + savefile->ReadString( declName ); + + // Initialize beam data from the beam file + decl = (const hhDeclBeam*)declManager->FindType(DECL_BEAM, declName, false); + HH_ASSERT( renderEntity.declBeam == decl ); + + // Initialize the random number generator + int seed; + savefile->ReadInt( seed ); + random.SetSeed( seed ); + + savefile->ReadFloat( beamTime ); + + // Allocate the beams + if( beamList ) { + delete[] beamList; + } + beamList = new hhBeam[decl->numBeams]; + + for( int i = 0; i < decl->numBeams; i++) { + beamList[i].Restore( savefile, this, &renderEntity.beamNodes[i]); + } + + savefile->ReadMat3( beamAxis ); + savefile->ReadVec3( arcVector ); + savefile->ReadVec3( targetLocation ); + targetEntity.Restore( savefile ); + savefile->ReadVec3( targetEntityOffset ); + savefile->ReadInt( targetEntityId ); + savefile->ReadFloat( beamLength ); + savefile->ReadBool( bRigidBeamLength ); + savefile->ReadFloat( phaseScale ); + savefile->ReadFloat( offsetScale ); + savefile->ReadBool( bActive ); + + Activate(bActive); +} + diff --git a/neo/prey/Prey/prey_beam.h b/neo/prey/Prey/prey_beam.h new file mode 100644 index 00000000..6943cadc --- /dev/null +++ b/neo/prey/Prey/prey_beam.h @@ -0,0 +1,206 @@ + +#ifndef __PREY_BEAM_H__ +#define __PREY_BEAM_H__ + +#define MAX_BEAM_SPLINE_CONTROLS 6 +#define BEAM_SPLINE_CONTROL_STEP ( 1.0f / ( MAX_BEAM_SPLINE_CONTROLS - 1 ) ) +#define EXTRA_SPLINE_CONTROLS 2 + +#define BEAM_HZ 30 +#define BEAM_MSEC (1000 / BEAM_HZ) + +extern const idEventDef EV_ToggleBeamLength; + +class hhBeamSystem; +class hhBeam; +class hhDeclBeam; + +/* +class beamCmd { +public: + virtual void Execute( hhBeam *beam ); +}; + +// BEAM COMMANDS +class beamCmd_SplineLinearToTarget : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_SplineArcToTarget : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_SplineAdd : public beamCmd { +public: + void Execute( hhBeam *beam ); + + int index; + idVec3 offset; +}; + +class beamCmd_SplineAddSin : public beamCmd_SplineAdd { +public: + void Execute( hhBeam *beam ); + + idVec3 phase; +}; + +class beamCmd_SplineAddSinTime : public beamCmd_SplineAddSin { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_SplineAddSinTimeScaled : public beamCmd_SplineAddSin { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_ConvertSplineToNodes : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_NodeLinearToTarget : public beamCmd { +public: + void Execute( hhBeam *beam ); +}; + +class beamCmd_NodeElectric : public beamCmd { +public: + void Execute( hhBeam *beam ); + + idVec3 offset; + bool bNotEnds; +}; +// END BEAM COMMANDS +*/ + +class hhBeam { +public: + hhBeam(); + ~hhBeam(); + void Init( hhBeamSystem *newSystem, hhBeamNodes_t *newInfo ); + + idBounds GetBounds(); + + void VerifyNodeIndex(int index, const char *functionName); + void VerifySplineIndex(int index, const char *functionName); + idVec3 NodeGet(int index); + void NodeSet(int index, idVec3 value); + idVec3 SplineGet(int index); + void SplineSet(int index, idVec3 value); + + void SplineLinear(idVec3 start, idVec3 end); + void SplineLinearToTarget( void ); + void SplineArc( idVec3 start, idVec3 end, idVec3 startVec ); + void SplineArcToTarget( void ); + + void SplineAddSin(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ); + void SplineAddSinTime(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ); + void SplineAddSinTimeScaled(int index, float phaseX, float phaseY, float phaseZ, float offsetX, float offsetY, float offsetZ); + void SplineAdd(int index, idVec3 offset); + void ConvertSplineToNodes(void); + + void NodeLinear(idVec3 start, idVec3 end); + void NodeLinearToTarget( void ); + void NodeAdd(int index, idVec3 offset); + void NodeElectric(float x, float y, float z, bool bNotEnds); + + void TransformNodes(void); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile, hhBeamSystem *newSystem, hhBeamNodes_t *newInfo ); + +protected: + idVec3 *nodeList; // index into renderEntity nodes + hhBeamSystem *system; + idVec3 splineList[MAX_BEAM_SPLINE_CONTROLS + EXTRA_SPLINE_CONTROLS]; +}; + + +class hhBeamSystem : public idEntity { +public: + CLASS_PROTOTYPE( hhBeamSystem ); + + void Spawn(void); + hhBeamSystem(); + ~hhBeamSystem(); + + virtual void Think(void); + virtual void UpdateModel( void ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual void InitSystem( const char* modelName ); + virtual float GetBeamTime( void ) { return beamTime; } + virtual void SetTargetLocation(idVec3 newLoc); + virtual void SetTargetEntity( idEntity *ent, int traceId=0, idVec3 &offset=vec3_origin ); + virtual void SetTargetEntity( idEntity *ent, const char *boneName, idVec3 &offset=vec3_origin ); + + virtual bool IsActivated() const { return bActive; } + virtual void Activate( const bool bActivate ); + + virtual idVec3 GetTargetLocation( void ); + virtual idMat3 GetBeamAxis( void ) { return( beamAxis ); } + + virtual void SetArcVector( idVec3 vec ) { arcVector = vec; } + virtual idVec3 GetArcVector( void ) { return( arcVector ); } + + virtual float GetBeamPhaseScale( void ) { return phaseScale; } + virtual void SetBeamPhaseScale( float scale ) { phaseScale = scale; } + virtual float GetBeamOffsetScale( void ) { return offsetScale; } + virtual void SetBeamOffsetScale( float scale ) { offsetScale = scale; } + + //AOB + virtual void SetAxis( const idMat3 &axis ); + virtual float GetBeamLength( void ) { return( beamLength ); } + virtual void SetBeamLength( const float length ); + virtual void ToggleBeamLength( bool rigid ) { bRigidBeamLength = rigid; } + + virtual void ExecuteBeam( int index, hhBeam *beam ); + + static hhBeamSystem* SpawnBeam( const idVec3& start, const char* modelName, const idMat3& axis = mat3_identity, bool pureLocal = false ); + + idRandom random; + idStr declName; + const hhDeclBeam *decl; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - for functionality regarding client entity beams. + idEntityPtr snapshotOwner; +protected: + virtual void Event_Activate( idEntity *activator ); + void Event_SetBeamPhaseScale( float scale ); // bg + void Event_SetBeamOffsetScale( float scale ); // bg + + float beamTime; + hhBeam *beamList; + + idMat3 beamAxis; + idVec3 arcVector; // For SplineArc + + idVec3 targetLocation; + idEntityPtr targetEntity; + idVec3 targetEntityOffset; + int targetEntityId; + + //AOB + float beamLength; + bool bRigidBeamLength; + + // Used to scale the phase/offset of the beam in code + float phaseScale; + float offsetScale; + +protected: + bool bActive; +}; + +#endif /* __PREY_BEAM_H__ */ diff --git a/neo/prey/Prey/prey_bonecontroller.cpp b/neo/prey/Prey/prey_bonecontroller.cpp new file mode 100644 index 00000000..6e5bd789 --- /dev/null +++ b/neo/prey/Prey/prey_bonecontroller.cpp @@ -0,0 +1,247 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/********************************************************************** + +hhBoneController + +**********************************************************************/ + +/* +================ +hhBoneController::hhBoneController +================ +*/ +hhBoneController::hhBoneController() { + m_JointHandle = INVALID_JOINT; + m_pOwner = NULL; + + m_Factor.Set(1.0f, 1.0f, 1.0f); + m_TurnRate.Set(0.0f, 0.0f, 0.0f); + + m_IdealAng.Zero(); + m_CurrentAng.Zero(); + m_MinAngles.Zero(); + m_MaxAngles.Zero(); + m_Fraction.Zero(); + m_MaxDelta.Zero(); +} + +/* +================ +hhBoneController::Setup +================ +*/ +void hhBoneController::Setup( idEntity *pOwner, const char *pJointname, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ) { + jointHandle_t Joint = ( pOwner ) ? pOwner->GetAnimator()->GetJointHandle( pJointname ) : INVALID_JOINT; + + Setup( pOwner, Joint, MinAngles, MaxAngles, Rate, Factor ); +} + +/* +================ +hhBoneController::Setup +================ +*/ +void hhBoneController::Setup( idEntity *pOwner, jointHandle_t Joint, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ) { + m_IdealAng.Zero(); + m_CurrentAng.Zero(); + m_Fraction.Zero(); + + m_JointHandle = Joint; + + m_MinAngles = MinAngles; + m_MaxAngles = MaxAngles; + m_Factor = Factor; + m_TurnRate = Rate; + m_pOwner = pOwner; +} + +/* +================ +hhBoneController::Update +================ +*/ +void hhBoneController::Update( int iCurrentTime ) { + idAngles Delta; + int iIndex; + idAngles Turn; + idAngles Angle; + + if ( !m_pOwner || + ( m_JointHandle == INVALID_JOINT ) ) { + return; + } + + Turn = m_TurnRate * MS2SEC(gameLocal.msec) * gameLocal.GetTimeScale(); + + Delta = m_IdealAng - m_CurrentAng; + for( iIndex = 0; iIndex < 3; ++iIndex ) { + if ( Delta[ iIndex ] > Turn[iIndex] ) { + Delta[ iIndex ] = Turn[iIndex]; + } + if ( Delta[ iIndex ] < -Turn[iIndex] ) { + Delta[ iIndex ] = -Turn[iIndex]; + } + } + + m_CurrentAng += Delta; + for(iIndex = 0; iIndex < 3; ++iIndex) { + Angle[iIndex] = m_CurrentAng[iIndex] * m_Factor[iIndex]; + } + + m_pOwner->GetAnimator()->SetJointAxis( m_JointHandle, JOINTMOD_WORLD, Angle.ToMat3() ); + + m_Fraction.pitch = 1.0f - ( (m_MaxDelta.pitch > VECTOR_EPSILON) ? idMath::Fabs((m_IdealAng.pitch - m_CurrentAng.pitch)) / m_MaxDelta.pitch : 0.0f ); + m_Fraction.yaw = 1.0f - ( (m_MaxDelta.yaw > VECTOR_EPSILON) ? idMath::Fabs((m_IdealAng.yaw - m_CurrentAng.yaw)) / m_MaxDelta.yaw : 0.0f ); + m_Fraction.roll = 1.0f - ( (m_MaxDelta.roll > VECTOR_EPSILON) ? idMath::Fabs((m_IdealAng.roll - m_CurrentAng.roll)) / m_MaxDelta.roll : 0.0f ); +} + +/* +================ +hhBoneController::TurnTo +================ +*/ +bool hhBoneController::TurnTo( idAngles &Target ) { + m_IdealAng = Target; + bool bClampAngles = !ClampAngles(); + + m_MaxDelta.pitch = idMath::Fabs(m_IdealAng.pitch - m_CurrentAng.pitch); + m_MaxDelta.yaw = idMath::Fabs(m_IdealAng.yaw - m_CurrentAng.yaw); + m_MaxDelta.roll = idMath::Fabs(m_IdealAng.roll - m_CurrentAng.roll); + + return bClampAngles; +} + +/* +================ +hhBoneController::AimAt +================ +*/ +bool hhBoneController::AimAt( idVec3 &Target ) { + idVec3 dir; + idVec3 localDir; + + if ( !m_pOwner ) { + return false; + } + + dir = Target - m_pOwner->GetOrigin(); + dir.Normalize(); + + m_pOwner->GetAxis().ProjectVector( dir, localDir ); + + m_IdealAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() ); + m_IdealAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + + bool bClampAngles = !ClampAngles(); + + m_MaxDelta.pitch = idMath::Fabs(m_IdealAng.pitch - m_CurrentAng.pitch); + m_MaxDelta.yaw = idMath::Fabs(m_IdealAng.yaw - m_CurrentAng.yaw); + + return bClampAngles; +} + +/* +===================== +hhBoneController::ClampAngles +===================== +*/ +bool hhBoneController::ClampAngles( void ) { + int i; + bool clamp; + + clamp = false; + for( i = 0; i < 3; i++ ) { + if ( m_IdealAng[ i ] > m_MaxAngles[ i ] ) { + m_IdealAng[ i ] = m_MaxAngles[ i ]; + clamp = true; + } + if ( m_IdealAng[ i ] < m_MinAngles[ i ] ) { + m_IdealAng[ i ] = m_MinAngles[ i ]; + clamp = true; + } + } + + return clamp; +} + +/* +===================== +hhBoneController::SetRotationFactor +===================== +*/ +void hhBoneController::SetRotationFactor(idAngles& RotationFactor) { + m_Factor = RotationFactor; +} + +/* +===================== +hhBoneController::IsFinishedMoving +===================== +*/ +bool hhBoneController::IsFinishedMoving(int iAxis) { + return (m_IdealAng[iAxis] == m_CurrentAng[iAxis]); +} + +/* +===================== +hhBoneController::IsFinishedMoving +===================== +*/ +bool hhBoneController::IsFinishedMoving() { + return (m_IdealAng == m_CurrentAng); +} + +/* +===================== +hhBoneController::AdjustScanRateToLinearizeBonePath +===================== +*/ +void hhBoneController::AdjustScanRateToLinearizeBonePath(float fLinearScanRate) { + float fDeltaPitch = idMath::Fabs( (m_IdealAng.pitch - m_CurrentAng.pitch) * m_Factor.pitch ); + float fDeltaYaw = idMath::Fabs( (m_IdealAng.yaw - m_CurrentAng.yaw) * m_Factor.yaw ); + + float fHypotenuse = idMath::Sqrt(fDeltaPitch * fDeltaPitch + fDeltaYaw * fDeltaYaw); + if(fHypotenuse) { + float fFrac = fLinearScanRate / fHypotenuse; + + m_TurnRate.pitch = fDeltaPitch * fFrac; + m_TurnRate.yaw = fDeltaYaw * fFrac; + } +} + +//================ +//hhBoneController::Save +//================ +void hhBoneController::Save( idSaveGame *savefile ) const { + savefile->WriteAngles( m_Fraction ); + savefile->WriteAngles( m_MaxDelta ); + savefile->WriteInt( m_JointHandle ); + savefile->WriteAngles( m_IdealAng ); + savefile->WriteAngles( m_CurrentAng ); + savefile->WriteAngles( m_MinAngles ); + savefile->WriteAngles( m_MaxAngles ); + savefile->WriteAngles( m_Factor ); + savefile->WriteAngles( m_TurnRate ); + savefile->WriteObject( m_pOwner ); +} + +//================ +//hhBoneController::Restore +//================ +void hhBoneController::Restore( idRestoreGame *savefile ) { + savefile->ReadAngles( m_Fraction ); + savefile->ReadAngles( m_MaxDelta ); + savefile->ReadInt( reinterpret_cast ( m_JointHandle ) ); + savefile->ReadAngles( m_IdealAng ); + savefile->ReadAngles( m_CurrentAng ); + savefile->ReadAngles( m_MinAngles ); + savefile->ReadAngles( m_MaxAngles ); + savefile->ReadAngles( m_Factor ); + savefile->ReadAngles( m_TurnRate ); + savefile->ReadObject( reinterpret_cast ( m_pOwner ) ); +} + diff --git a/neo/prey/Prey/prey_bonecontroller.h b/neo/prey/Prey/prey_bonecontroller.h new file mode 100644 index 00000000..421fd6bf --- /dev/null +++ b/neo/prey/Prey/prey_bonecontroller.h @@ -0,0 +1,57 @@ +#ifndef __HH_BONE_CONTROLLER_H +#define __HH_BONE_CONTROLLER_H + +/*********************************************************************** + + hhBoneController + +***********************************************************************/ + +class hhBoneController { + public: + hhBoneController(); + + void SetTurnRate( idAngles& Rate ) { m_TurnRate = Rate; } + const idAngles& GetTurnRate() const { return m_TurnRate; } + + void SetRotationFactor( idAngles& RotationFactor ); + const idAngles& GetRotationFactor() const { return m_Factor; } + + void Setup( idEntity *pOwner, const char *pJointname, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ); + void Setup( idEntity *pOwner, jointHandle_t Joint, idAngles &MinAngles, idAngles &MaxAngles, idAngles& Rate, idAngles& Factor ); + void Update( int iCurrentTime ); + bool TurnTo( idAngles &Target ); + bool AimAt( idVec3 &Target ); + bool IsFinishedMoving( int iAxis ); + bool IsFinishedMoving(); + + void AdjustScanRateToLinearizeBonePath( float fLinearScanRate ); + + bool Add( idAngles &Ang ) { return TurnTo( Ang + m_IdealAng ); }; + void Clear( void ) { m_IdealAng.Zero(); }; + const idAngles &CurrentAngles() const { return m_CurrentAng; }; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + bool ClampAngles( void ); + + public: + idAngles m_Fraction; + + protected: + idAngles m_MaxDelta; + + jointHandle_t m_JointHandle; + idAngles m_IdealAng; + idAngles m_CurrentAng; + idAngles m_MinAngles; + idAngles m_MaxAngles; + + idAngles m_Factor; + idAngles m_TurnRate; + idEntity *m_pOwner; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_camerainterpolator.cpp b/neo/prey/Prey/prey_camerainterpolator.cpp new file mode 100644 index 00000000..68bb75fe --- /dev/null +++ b/neo/prey/Prey/prey_camerainterpolator.cpp @@ -0,0 +1,672 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define CAMERA_INTERP_LERP_DEBUG if( p_camInterpDebug.GetInteger() == 1 ) gameLocal.Printf +#define CAMERA_INTERP_SLERP_DEBUG if( p_camInterpDebug.GetInteger() == 2 ) gameLocal.Printf + +/********************************************************************** + +hhCameraInterpolator + +**********************************************************************/ + +/* +================ +NoInterpEvaluate +================ +*/ +float NoInterpEvaluate( float& interpVal, float deltaVal ) { + return 1.0f; +} + +/* +================ +VariableMidPointSinusoidalEvaluate +================ +*/ +float VariableMidPointSinusoidalEvaluate( float& interpVal, float deltaVal ) { + interpVal = hhMath::ClampFloat( 0.0f, 1.0f, interpVal + deltaVal ); + + if( interpVal <= 0.0f || interpVal >= 1.0f ) { + return interpVal; + } + + float debug = hhMath::Sin( DEG2RAD(hhMath::MidPointLerp(0.0f, 60.0f, 90.0f, interpVal)) ); + return debug; +} + +/* +================ +LinearEvaluate +================ +*/ +float LinearEvaluate( float& interpVal, float deltaVal ) { + interpVal = hhMath::ClampFloat( 0.0f, 1.0f, interpVal + deltaVal ); + + if( interpVal <= 0.0f || interpVal >= 1.0f ) { + return interpVal; + } + + return interpVal; +} + +/* +================ +InverseEvaluate +================ +*/ +float InverseEvaluate( float& interpVal, float deltaVal ) { + interpVal = hhMath::ClampFloat( 0.0f, 1.0f, interpVal + deltaVal ); + + if( interpVal <= 0.0f || interpVal >= 1.0f ) { + return interpVal; + } + + const float minLerpVal = deltaVal; + if( deltaVal <= 0.0f ) { + return interpVal; + } + + const float scale = (1.0f / minLerpVal) - 1.0f; + float debug = ((1.0f / interpVal) - 1.0f) / scale; + return debug; +} + +/* +================ +hhCameraInterpolator::hhCameraInterpolator +================ +*/ +hhCameraInterpolator::hhCameraInterpolator() : + clipBounds( idBounds(idVec3(-1, -1, 10), idVec3(1, 1, 12)) ) { + + ClearFuncList(); + RegisterFunc( NoInterpEvaluate, IT_None ); + RegisterFunc( VariableMidPointSinusoidalEvaluate, IT_VariableMidPointSinusoidal ); + RegisterFunc( LinearEvaluate, IT_Linear ); + RegisterFunc( InverseEvaluate, IT_Inverse ); + + SetSelf( NULL ); + Setup( 0.0f, IT_None ); + Reset( vec3_origin, mat3_identity[2], 0.0f ); +} + +/* +================ +hhCameraInterpolator::SetSelf +================ +*/ +void hhCameraInterpolator::SetSelf( hhPlayer* self ) { + this->self = self; + clipBounds.SetOwner( self ); +} + +/* +================ +hhCameraInterpolator::GetCurrentEyeHeight +================ +*/ +float hhCameraInterpolator::GetCurrentEyeHeight() const { + return eyeOffsetInfo.current; +} + +/* +================ +hhCameraInterpolator::GetIdealEyeHeight +================ +*/ +float hhCameraInterpolator::GetIdealEyeHeight() const { + return eyeOffsetInfo.end; +} + +/* +================ +hhCameraInterpolator::GetCurrentEyeOffset +================ +*/ +idVec3 hhCameraInterpolator::GetCurrentEyeOffset() const { + return GetCurrentUpVector() * GetCurrentEyeHeight(); +} + +/* +================ +hhCameraInterpolator::GetEyePosition +================ +*/ +idVec3 hhCameraInterpolator::GetEyePosition() const { + return GetCurrentPosition() + GetCurrentEyeOffset(); +} + +/* +================ +hhCameraInterpolator::GetCurrentPosition +================ +*/ +idVec3 hhCameraInterpolator::GetCurrentPosition() const { + return positionInfo.current; +} + +/* +================ +hhCameraInterpolator::GetIdealPosition +================ +*/ +idVec3 hhCameraInterpolator::GetIdealPosition() const { + return positionInfo.end; +} + +/* +================ +hhCameraInterpolator::GetCurrentUpVector +================ +*/ +idVec3 hhCameraInterpolator::GetCurrentUpVector() const { + return GetCurrentAxis()[2]; +} + +/* +================ +hhCameraInterpolator::GetCurrentAxis +================ +*/ +idMat3 hhCameraInterpolator::GetCurrentAxis() const { + return GetCurrentRotation().ToMat3(); +} + +/* +================ +hhCameraInterpolator::GetCurrentAngles +================ +*/ +idAngles hhCameraInterpolator::GetCurrentAngles() const { + return GetCurrentRotation().ToAngles(); +} + +/* +================ +hhCameraInterpolator::GetCurrentRotation +================ +*/ +idQuat hhCameraInterpolator::GetCurrentRotation() const { + return rotationInfo.current; +} + +/* +================ +hhCameraInterpolator::GetIdealUpVector +================ +*/ +idVec3 hhCameraInterpolator::GetIdealUpVector() const { + return GetIdealAxis()[2]; +} + +/* +================ +hhCameraInterpolator::GetIdealAxis +================ +*/ +idMat3 hhCameraInterpolator::GetIdealAxis() const { + return GetIdealRotation().ToMat3(); +} + +/* +================ +hhCameraInterpolator::GetIdealAngles +================ +*/ +idAngles hhCameraInterpolator::GetIdealAngles() const { + return GetIdealRotation().ToAngles(); +} + +/* +================ +hhCameraInterpolator::GetIdealRotation +================ +*/ +idQuat hhCameraInterpolator::GetIdealRotation() const { + return rotationInfo.end; +} + +/* +================ +hhCameraInterpolator::UpdateViewAngles +================ +*/ +idAngles hhCameraInterpolator::UpdateViewAngles( const idAngles& viewAngles ) { + return (viewAngles.ToMat3() * GetCurrentAxis()).ToAngles(); +} + +/* +================ +hhCameraInterpolator::UpdateTarget +================ +*/ +void hhCameraInterpolator::UpdateTarget( const idVec3& idealPos, const idMat3& idealAxis, float eyeOffset, int interpFlags ) { + if( !positionInfo.end.Compare(idealPos, VECTOR_EPSILON) ) { + SetTargetPosition( idealPos, interpFlags ); + } + + if( !idealAxis[2].Compare(GetIdealUpVector(), VECTOR_EPSILON) ) { + SetTargetAxis( idealAxis, interpFlags ); + } + + if( hhMath::Fabs(eyeOffset - hhMath::Fabs(eyeOffsetInfo.end)) >= VECTOR_EPSILON ) { + SetTargetEyeOffset( eyeOffset, interpFlags ); + } +} + +/* +================ +hhCameraInterpolator::SetTargetPosition +================ +*/ +void hhCameraInterpolator::SetTargetPosition( const idVec3& idealPos, int interpFlags ) { + if( interpFlags & INTERPOLATE_POSITION ) { + positionInfo.Set( idealPos ); + } else { + positionInfo.start = idealPos - (positionInfo.end - positionInfo.start); + positionInfo.current = idealPos - (positionInfo.end - positionInfo.current); + positionInfo.end = idealPos; + } +} + +/* +================ +hhCameraInterpolator::SetTargetAxis +================ +*/ +void hhCameraInterpolator::SetTargetAxis( const idMat3& idealAxis, int interpFlags ) { + idQuat cachedRotation; + idQuat idealRotation = DetermineIdealRotation( idealAxis[2] ); + if( interpFlags & INTERPOLATE_ROTATION ) { + rotationInfo.Set( idealRotation ); + } else { + cachedRotation = idealRotation * rotationInfo.end.Inverse(); + rotationInfo.start = cachedRotation * rotationInfo.start; + rotationInfo.current = cachedRotation * rotationInfo.current; + rotationInfo.end = idealRotation; + } +} + +/* +================ +hhCameraInterpolator::SetTargetEyeOffset +================ +*/ +void hhCameraInterpolator::SetTargetEyeOffset( float idealEyeOffset, int interpFlags ) { + if( interpFlags & INTERPOLATE_EYEOFFSET ) { + eyeOffsetInfo.Set( idealEyeOffset ); + } +} + +/* +================ +hhCameraInterpolator::SetInterpolationType +================ +*/ +InterpolationType hhCameraInterpolator::SetInterpolationType( InterpolationType type ) { + InterpolationType cachedType = interpType; + + interpType = type; + + return cachedType; +} + +/* +================ +hhCameraInterpolator::Setup +================ +*/ +void hhCameraInterpolator::Setup( const float lerpScale, const InterpolationType type ) { + this->lerpScale = hhMath::hhMax( 0.01f, lerpScale ); + SetInterpolationType( type ); +} + +/* +================ +hhCameraInterpolator::Reset +================ +*/ +void hhCameraInterpolator::Reset( const idVec3& position, const idVec3& idealUpVector, float eyeOffset ) { + positionInfo.Reset( position ); + rotationInfo.Reset( DetermineIdealRotation(idealUpVector) ); + eyeOffsetInfo.Reset( eyeOffset ); +} + +/* +================ +hhCameraInterpolator::DetermineIdealRotation +================ +*/ +idQuat hhCameraInterpolator::DetermineIdealRotation( const idVec3& idealUpVector, const idVec3& viewDir, const idMat3& untransformedViewAxis ) { + idMat3 mat; + idVec3 newViewVector( viewDir ); + + newViewVector.ProjectOntoPlane( idealUpVector ); + if( newViewVector.LengthSqr() < VECTOR_EPSILON ) { + newViewVector = -Sign( newViewVector * idealUpVector ); + } + + newViewVector.Normalize(); + mat[0] = newViewVector; + mat[1] = idealUpVector.Cross( newViewVector ); + mat[2] = idealUpVector; + + mat = untransformedViewAxis.Transpose() * mat; + return mat.ToQuat(); +} + +/* +================ +hhCameraInterpolator::DetermineIdealRotation +================ +*/ +idQuat hhCameraInterpolator::DetermineIdealRotation( const idVec3& idealUpVector ) { + if( !self ) { + return mat3_identity.ToQuat(); + } + + return DetermineIdealRotation( idealUpVector, self->GetAxis()[0], self->GetUntransformedViewAxis() ); +} + +/* +================ +hhCameraInterpolator::ClearFuncList +================ +*/ +void hhCameraInterpolator::ClearFuncList() { + funcList.SetNum( IT_NumTypes ); + + for( int ix = 0; ix < funcList.Num(); ++ix ) { + funcList[ix] = NULL; + } +} + +/* +================ +hhCameraInterpolator::RegisterFunc +================ +*/ +void hhCameraInterpolator::RegisterFunc( InterpFunc func, InterpolationType type ) { + int index = (int)type; + + if( funcList[index] ) { + gameLocal.Warning( "Function already registered for interpolation type %d", type ); + } + + funcList[index] = func; +} + +/* +================ +hhCameraInterpolator::DetermineFunc +================ +*/ +InterpFunc hhCameraInterpolator::DetermineFunc( InterpolationType type ) { + int index = (int)( p_disableCamInterp.GetBool() ? IT_None : type ); + return funcList[ index ]; +} + +/* +================ +hhCameraInterpolator::Evaluate +================ +*/ +void hhCameraInterpolator::Evaluate( float deltaTime ) { + float baseTime = deltaTime * gameLocal.GetTimeScale(); + float scaledDeltaTime = baseTime * lerpScale; + float lenFactor = (positionInfo.current-positionInfo.end).Length()*0.2f; + if (lenFactor < 1.0f) { + lenFactor = 1.0f; + } + float scaledPosDeltaTime = baseTime * (lerpScale*lenFactor); + float scaledEyeDeltaTime = baseTime * (lerpScale*4.0f); + + InterpFunc func = DetermineFunc( interpType ); + if( !func ) { + return; + } + + EvaluatePosition( func(positionInfo.interpVal, scaledPosDeltaTime) ); + EvaluateRotation( func(rotationInfo.interpVal, scaledDeltaTime) ); + EvaluateEyeOffset( func(eyeOffsetInfo.interpVal, scaledEyeDeltaTime) ); + + VerifyEyeOffset( eyeOffsetInfo.current ); +} + +/* +================ +hhCameraInterpolator::EvaluatePosition +================ +*/ +void hhCameraInterpolator::EvaluatePosition( float interpVal ) { + if( interpVal >= 1.0f ) { + positionInfo.Reset( positionInfo.end ); + return; + } + + positionInfo.current.Lerp( positionInfo.start, positionInfo.end, interpVal ); +} + +/* +================ +hhCameraInterpolator::EvaluateRotation +================ +*/ +void hhCameraInterpolator::EvaluateRotation( float interpVal ) { + if( interpVal >= 1.0f ) { + rotationInfo.Reset( rotationInfo.end ); + return; + } + + rotationInfo.current.Slerp( rotationInfo.start, rotationInfo.end, interpVal ); +} + +/* +================ +hhCameraInterpolator::EvaluateEyeOffset +================ +*/ +void hhCameraInterpolator::EvaluateEyeOffset( float interpVal ) { + if( interpVal >= 1.0f ) { + eyeOffsetInfo.Reset( eyeOffsetInfo.end ); + return; + } + + eyeOffsetInfo.current = hhMath::Lerp( eyeOffsetInfo.start, eyeOffsetInfo.end, interpVal ); +} + +/* +================ +hhCameraInterpolator::VerifyEyeOffset +================ +*/ +void hhCameraInterpolator::VerifyEyeOffset( float& eyeOffset ) { + idPhysics* selfPhysics = NULL; + + if( !self ) { + return; + } + + selfPhysics = self->GetPhysics(); + if( !selfPhysics ) { + return; + } + + if( clipBounds.GetBounds().Translate(GetCurrentUpVector() * eyeOffset).IntersectsBounds(selfPhysics->GetBounds()) ) { + return; + } + + trace_t trace; + gameLocal.clip.Translation( trace, GetCurrentPosition(), GetCurrentPosition() + GetCurrentUpVector() * eyeOffset, &clipBounds, GetCurrentAxis(), selfPhysics->GetClipMask(), NULL ); + eyeOffset *= trace.fraction; +} + +//================ +//hhCameraInterpolator::Save +//================ +void hhCameraInterpolator::Save( idSaveGame *savefile ) const { + savefile->WriteQuat( rotationInfo.start ); + savefile->WriteQuat( rotationInfo.end ); + savefile->WriteQuat( rotationInfo.current ); + savefile->WriteFloat( rotationInfo.interpVal ); + + savefile->WriteVec3( positionInfo.start ); + savefile->WriteVec3( positionInfo.end ); + savefile->WriteVec3( positionInfo.current ); + savefile->WriteFloat( positionInfo.interpVal ); + + savefile->WriteFloat( eyeOffsetInfo.start ); + savefile->WriteFloat( eyeOffsetInfo.end ); + savefile->WriteFloat( eyeOffsetInfo.current ); + savefile->WriteFloat( eyeOffsetInfo.interpVal ); + + savefile->WriteInt( reinterpret_cast ( interpType ) ); + savefile->WriteFloat( lerpScale ); + savefile->WriteObject( self ); + + clipBounds.Save( savefile ); +} + +//================ +//hhCameraInterpolator::Restore +//================ +void hhCameraInterpolator::Restore( idRestoreGame *savefile ) { + savefile->ReadQuat( rotationInfo.start ); + savefile->ReadQuat( rotationInfo.end ); + savefile->ReadQuat( rotationInfo.current ); + savefile->ReadFloat( rotationInfo.interpVal ); + + savefile->ReadVec3( positionInfo.start ); + savefile->ReadVec3( positionInfo.end ); + savefile->ReadVec3( positionInfo.current ); + savefile->ReadFloat( positionInfo.interpVal ); + + savefile->ReadFloat( eyeOffsetInfo.start ); + savefile->ReadFloat( eyeOffsetInfo.end ); + savefile->ReadFloat( eyeOffsetInfo.current ); + savefile->ReadFloat( eyeOffsetInfo.interpVal ); + + savefile->ReadInt( reinterpret_cast ( interpType ) ); + savefile->ReadFloat( lerpScale ); + savefile->ReadObject( reinterpret_cast ( self ) ); + + clipBounds.Restore( savefile ); +} + +//================ +//hhCameraInterpolator::WriteToSnapshot +//================ +void hhCameraInterpolator::WriteToSnapshot( idBitMsgDelta &msg, const hhPlayer *pl ) const +{ + idCQuat sq, q; + + idVec3 plPos = ((idPlayer *)pl)->GetPlayerPhysics()->GetOrigin(); +#if 1 + sq = rotationInfo.start.ToCQuat(); + msg.WriteFloat(sq.x); + msg.WriteFloat(sq.y); + msg.WriteFloat(sq.z); + q = rotationInfo.current.ToCQuat(); + msg.WriteDeltaFloat(sq.x, q.x); + msg.WriteDeltaFloat(sq.y, q.y); + msg.WriteDeltaFloat(sq.z, q.z); + q = rotationInfo.end.ToCQuat(); + msg.WriteDeltaFloat(sq.x, q.x); + msg.WriteDeltaFloat(sq.y, q.y); + msg.WriteDeltaFloat(sq.z, q.z); + msg.WriteFloat(rotationInfo.interpVal, 4, 4); + + msg.WriteDeltaFloat(plPos[0], positionInfo.start[0]); + msg.WriteDeltaFloat(plPos[1], positionInfo.start[1]); + msg.WriteDeltaFloat(plPos[2], positionInfo.start[2]); + msg.WriteDeltaFloat(positionInfo.start[0], positionInfo.current[0]); + msg.WriteDeltaFloat(positionInfo.start[1], positionInfo.current[1]); + msg.WriteDeltaFloat(positionInfo.start[2], positionInfo.current[2]); + msg.WriteDeltaFloat(positionInfo.start[0], positionInfo.end[0]); + msg.WriteDeltaFloat(positionInfo.start[1], positionInfo.end[1]); + msg.WriteDeltaFloat(positionInfo.start[2], positionInfo.end[2]); + msg.WriteFloat(positionInfo.interpVal, 4, 4); + + msg.WriteFloat(eyeOffsetInfo.start); + msg.WriteDeltaFloat(eyeOffsetInfo.start, eyeOffsetInfo.current); + msg.WriteDeltaFloat(eyeOffsetInfo.start, eyeOffsetInfo.end); + msg.WriteFloat(eyeOffsetInfo.interpVal, 4, 4); +#else + q = rotationInfo.current.ToCQuat(); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + msg.WriteDeltaFloat(plPos[0], positionInfo.current[0]); + msg.WriteDeltaFloat(plPos[1], positionInfo.current[1]); + msg.WriteDeltaFloat(plPos[2], positionInfo.current[2]); + + msg.WriteFloat(eyeOffsetInfo.current); +#endif + + //msg.WriteFloat(lerpScale); +} + +//================ +//hhCameraInterpolator::ReadFromSnapshot +//================ +void hhCameraInterpolator::ReadFromSnapshot( const idBitMsgDelta &msg, hhPlayer *pl ) +{ + idCQuat sq, q; + + idVec3 plPos = pl->GetPlayerPhysics()->GetOrigin(); + +#if 1 + sq.x = msg.ReadFloat(); + sq.y = msg.ReadFloat(); + sq.z = msg.ReadFloat(); + rotationInfo.start = sq.ToQuat(); + q.x = msg.ReadDeltaFloat(sq.x); + q.y = msg.ReadDeltaFloat(sq.y); + q.z = msg.ReadDeltaFloat(sq.z); + rotationInfo.current = q.ToQuat(); + q.x = msg.ReadDeltaFloat(sq.x); + q.y = msg.ReadDeltaFloat(sq.y); + q.z = msg.ReadDeltaFloat(sq.z); + rotationInfo.end = q.ToQuat(); + rotationInfo.interpVal = msg.ReadFloat(4, 4); + + positionInfo.start[0] = msg.ReadDeltaFloat(plPos[0]); + positionInfo.start[1] = msg.ReadDeltaFloat(plPos[1]); + positionInfo.start[2] = msg.ReadDeltaFloat(plPos[2]); + positionInfo.current[0] = msg.ReadDeltaFloat(positionInfo.start[0]); + positionInfo.current[1] = msg.ReadDeltaFloat(positionInfo.start[1]); + positionInfo.current[2] = msg.ReadDeltaFloat(positionInfo.start[2]); + positionInfo.end[0] = msg.ReadDeltaFloat(positionInfo.start[0]); + positionInfo.end[1] = msg.ReadDeltaFloat(positionInfo.start[1]); + positionInfo.end[2] = msg.ReadDeltaFloat(positionInfo.start[2]); + positionInfo.interpVal = msg.ReadFloat(4, 4); + + eyeOffsetInfo.start = msg.ReadFloat(); + eyeOffsetInfo.current = msg.ReadDeltaFloat(eyeOffsetInfo.start); + eyeOffsetInfo.end = msg.ReadDeltaFloat(eyeOffsetInfo.start); + eyeOffsetInfo.interpVal = msg.ReadFloat(4, 4); +#else + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + rotationInfo.current = q.ToQuat(); + + positionInfo.current[0] = msg.ReadDeltaFloat(plPos[0]); + positionInfo.current[1] = msg.ReadDeltaFloat(plPos[1]); + positionInfo.current[2] = msg.ReadDeltaFloat(plPos[2]); + + eyeOffsetInfo.current = msg.ReadFloat(); + + rotationInfo.start = rotationInfo.current; + rotationInfo.end = rotationInfo.current; + positionInfo.start = positionInfo.current; + positionInfo.end = positionInfo.current; + eyeOffsetInfo.start = eyeOffsetInfo.current; + eyeOffsetInfo.end = eyeOffsetInfo.current; +#endif + //lerpScale = msg.ReadFloat(); +} diff --git a/neo/prey/Prey/prey_camerainterpolator.h b/neo/prey/Prey/prey_camerainterpolator.h new file mode 100644 index 00000000..bd223961 --- /dev/null +++ b/neo/prey/Prey/prey_camerainterpolator.h @@ -0,0 +1,134 @@ +#ifndef __HH_CAMERA_INTERPOLATOR_H +#define __HH_CAMERA_INTERPOLATOR_H + +/********************************************************************** + +hhCameraInterpolator + +**********************************************************************/ +class hhCameraInterpolator; + +enum InterpolationType { + IT_None, + IT_VariableMidPointSinusoidal, + IT_Linear, + IT_Inverse, + IT_NumTypes +}; + +const int INTERPOLATE_NONE = 0; +const int INTERPOLATE_POSITION = BIT( 1 ); +const int INTERPOLATE_ROTATION = BIT( 2 ); +const int INTERPOLATE_EYEOFFSET = BIT( 3 ); + +const int INTERPMASK_ALL = -1; +const int INTERPMASK_WALLWALK = INTERPOLATE_POSITION | INTERPOLATE_ROTATION; + + +typedef float (*InterpFunc)( float&, float ); + +class hhPlayer; + +class hhCameraInterpolator { + public: + hhCameraInterpolator(); + + void SetSelf( hhPlayer* self ); + + float GetCurrentEyeHeight() const; + float GetIdealEyeHeight() const; + idVec3 GetCurrentEyeOffset() const; + idVec3 GetEyePosition() const; + idVec3 GetCurrentPosition() const; + idVec3 GetIdealPosition() const; + + idVec3 GetCurrentUpVector() const; + idMat3 GetCurrentAxis() const; + idAngles GetCurrentAngles() const; + idQuat GetCurrentRotation() const; + + idVec3 GetIdealUpVector() const; + idMat3 GetIdealAxis() const; + idAngles GetIdealAngles() const; + idQuat GetIdealRotation() const; + + void UpdateEyeOffset(); + + void UpdateTarget( const idVec3& idealPosition, const idMat3& idealAxis, const float eyeOffset, int interpFlags ); + void SetTargetPosition( const idVec3& idealPos, int interpFlags ); + void SetTargetAxis( const idMat3& idealAxis, int interpFlags ); + void SetTargetEyeOffset( float idealEyeOffset, int interpFlags ); + void SetIdealRotation( const idQuat& idealRotation ) { rotationInfo.end = idealRotation; } + + void Setup( const float lerpScale, const InterpolationType type ); + + void Reset( const idVec3& position, const idVec3& idealUpVector, float eyeOffset ); + + idAngles UpdateViewAngles( const idAngles& viewAngles ); + + InterpolationType SetInterpolationType( InterpolationType type ); + void Evaluate( float frameTime ); + + idQuat DetermineIdealRotation( const idVec3& idealUpVector, const idVec3& viewDir, const idMat3& untransformedViewAxis ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - send over net + void WriteToSnapshot( idBitMsgDelta &msg, const hhPlayer *pl ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg, hhPlayer *pl ); + + protected: + void ClearFuncList(); + void RegisterFunc( InterpFunc func, InterpolationType type ); + InterpFunc DetermineFunc( InterpolationType type ); + + idQuat DetermineIdealRotation( const idVec3& idealUpVector ); + + void EvaluatePosition( float interpVal ); + void EvaluateRotation( float interpVal ); + void EvaluateEyeOffset( float interpVal ); + + void VerifyEyeOffset( float& eyeOffset ); + + protected: + template + struct LerpInfo_t { + Type start; + Type end; + Type current; + float interpVal; + + void Set( Type endVal ) { + end = endVal; + start = current; + interpVal = 0.0f; + } + + void Reset( Type endVal ) { + end = endVal; + start = end; + current = start; + interpVal = 1.0f; + } + + void IsDone() const { + return interpVal >= 1.0f; + } + }; + + LerpInfo_t rotationInfo; + LerpInfo_t positionInfo; + LerpInfo_t eyeOffsetInfo; + + InterpolationType interpType; + + float lerpScale; + + hhPlayer* self; + idList funcList; + + idClipModel clipBounds; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_firecontroller.cpp b/neo/prey/Prey/prey_firecontroller.cpp new file mode 100644 index 00000000..60492203 --- /dev/null +++ b/neo/prey/Prey/prey_firecontroller.cpp @@ -0,0 +1,548 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +ABSTRACT_DECLARATION( idClass, hhFireController ) +END_CLASS + +/* +================ +hhFireController::hhFireController +================ +*/ +hhFireController::hhFireController() : muzzleFlashHandle(-1) { + // Register us so we can be saved -mdl + gameLocal.RegisterUniqueObject( this ); +} + +/* +================ +hhFireController::~hhFireController +================ +*/ +hhFireController::~hhFireController() { + gameLocal.UnregisterUniqueObject( this ); + SAFE_FREELIGHT( muzzleFlashHandle ); + Clear(); +} + +/* +================ +hhFireController::Init +================ +*/ +void hhFireController::Init( const idDict* viewDict ) { + const char *shader = NULL; + + Clear(); + + dict = viewDict; + + if( !dict ) { + return; + } + + ammoRequired = dict->GetInt( "ammoRequired" ); + + // set up muzzleflash render light + const idMaterial*flashShader; + idVec3 flashColor; + idVec3 flashTarget; + idVec3 flashUp; + idVec3 flashRight; + //HUMANHEAD: aob - changed from float to idVec3 + idVec3 flashRadius; + //HUMANHEAD END + bool flashPointLight; + + // get the projectile + SetProjectileDict( dict->GetString("def_projectile") ); + + dict->GetString( "mtr_flashShader", "muzzleflash", &shader ); + flashShader = declManager->FindMaterial( shader, false ); + flashPointLight = dict->GetBool( "flashPointLight", "1" ); + dict->GetVector( "flashColor", "0 0 0", flashColor ); + flashTime = SEC2MS( dict->GetFloat( "flashTime", "0.08" ) ); + flashTarget = dict->GetVector( "flashTarget" ); + flashUp = dict->GetVector( "flashUp" ); + flashRight = dict->GetVector( "flashRight" ); + + memset( &muzzleFlash, 0, sizeof( muzzleFlash ) ); + + muzzleFlash.pointLight = flashPointLight; + muzzleFlash.shader = flashShader; + muzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + //HUMANHEAD: aob + if( dict->GetFloat("flashRadius", "0", flashRadius[0]) ) { // if 0, no light will spawn + flashRadius[2] = flashRadius[1] = flashRadius[0]; + } else { + flashRadius = dict->GetVector( "flashSize" ); + //muzzleFlash.lightCenter.Set( flashRadius[0] * 0.5f, 0.0f, 0.0f ); + } + muzzleFlash.lightRadius = flashRadius; + //HUMANHEAD END + + muzzleFlash.noShadows = true; //HUMANHEAD bjk + + if ( !flashPointLight ) { + muzzleFlash.target = flashTarget; + muzzleFlash.up = flashUp; + muzzleFlash.right = flashRight; + muzzleFlash.end = flashTarget; + } + + //HUMANHEAD: aob + fireDelay = dict->GetFloat( "fireRate" ); + spread = DEG2RAD( dict->GetFloat("spread") ); + + yawSpread = dict->GetFloat("yawSpread"); + + bCrosshair = dict->GetBool("crosshair"); + + numProjectiles = dict->GetInt( "numProjectiles" ); + //HUMANHEAD END + + deferProjNum = 0; //HUMANHEAD rww + deferProjTime = 0; //HUMANHEAD rww +} + +/* +================ +hhFireController::Clear +================ +*/ +void hhFireController::Clear() { + dict = NULL; + + muzzleOrigin.Zero(); + muzzleAxis.Identity(); + + // weapon definition + numProjectiles = 0; + projectile = NULL; + projDictName = ""; + + memset( &muzzleFlash, 0, sizeof( muzzleFlash ) ); + SAFE_FREELIGHT( muzzleFlashHandle ); + + muzzleFlashEnd = 0;; + flashTime = 0; + + // ammo management + ammoRequired = 0; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + + fireDelay = 0.0f; + spread = 0.0f; + + yawSpread = 0.0f; + + bCrosshair = false; + + projectileMaxHalfDimension = 0.0f; +} + + +/* +================ +hhFireController::SetProjectileDict +================ +*/ +void hhFireController::SetProjectileDict( const char* name ) { + idVec3 mins; + idVec3 maxs; + projectileMaxHalfDimension = 1.0f; + + projDictName = name; + + if ( name[0] ) { + projectile = gameLocal.FindEntityDefDict( name, false ); + if ( !projectile ) { + gameLocal.Warning( "Unknown projectile '%s'", name ); + } else { + const char *spawnclass = projectile->GetString( "spawnclass" ); + idTypeInfo *cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::Type ) ) { + gameLocal.Error( "Invalid spawnclass '%s' on projectile '%s'", spawnclass, name ); + } + + mins = projectile->GetVector( "mins" ); + maxs = projectile->GetVector( "maxs" ); + projectileMaxHalfDimension = hhMath::hhMax( (maxs.y - mins.y) * 0.5f, (maxs.z - mins.z) * 0.5f ); + } + } else { + projectile = NULL; + } +} + +/* +================ +hhFireController::DetermineProjectileAxis +================ +*/ +idMat3 hhFireController::DetermineProjectileAxis( const idMat3& axis ) { + idVec3 dir = hhUtils::RandomSpreadDir( axis, spread ); + idAngles projectileAngles( dir.ToAngles() ); + + projectileAngles[YAW] += yawSpread*hhMath::Sin(gameLocal.random.RandomFloat()*hhMath::TWO_PI); //rww - seperate optional yaw spread + + projectileAngles[2] = axis.ToAngles()[2]; + + return projectileAngles.ToMat3(); +} + +/* +================ +hhFireController::LaunchProjectiles +================ +*/ +bool hhFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + if( !GetProjectileOwner() ) { + return false; + } + + // check if we're out of ammo or the clip is empty + if( !HasAmmo() ) { + return false; + } + + UseAmmo(); + + // calculate the muzzle position + CalculateMuzzlePosition( muzzleOrigin, muzzleAxis ); + + if ( !gameLocal.isClient || dict->GetBool("net_clientProjectiles", "1") ) { //HUMANHEAD rww - clientside projectiles, because our weapons make the god of bandwidth weep. + //HUMANHEAD: aob + idVec3 adjustedOrigin = AssureInsideCollisionBBox( muzzleOrigin, GetSelf()->GetAxis(), GetCollisionBBox(), projectileMaxHalfDimension ); + idMat3 aimAxis = DetermineAimAxis( adjustedOrigin, GetSelf()->GetAxis() ); + //HUMANHEAD END + + LaunchProjectiles( adjustedOrigin, aimAxis, pushVelocity, GetProjectileOwner() ); + + //rww - remove this from here and only do it on the client, we need to create the effect locally and with knowledge + //of if we should get the viewmodel bone or the worldmodel one + //CreateMuzzleFx( muzzleOrigin, muzzleAxis ); + } + + if (gameLocal.GetLocalPlayer()) + { //rww - create muzzle fx on client + idVec3 localMuzzleOrigin = muzzleOrigin; + idMat3 localMuzzleAxis = muzzleAxis; + if (gameLocal.isMultiplayer) { //rww - check if we should display the actual muzzle flash from a point different than the projectile launch location + idVec3 newOrigin; + idMat3 newAxis; + if (CheckThirdPersonMuzzle(newOrigin, newAxis)) { + localMuzzleOrigin = newOrigin; + localMuzzleAxis = newAxis; + } + } + CreateMuzzleFx( localMuzzleOrigin, localMuzzleAxis ); + } + + return true; +} + +/* +================ +hhFireController::CheckDeferredProjectiles +================ +*/ +void hhFireController::CheckDeferredProjectiles(void) { //rww + assert(!gameLocal.isClient); + if (deferProjTime > gameLocal.time) { + return; + } + + //FIXME compensate for intermediate time? + + deferProjTime = gameLocal.time + 50; //time is rather arbitrary. + + int i = 0; + while (deferProjNum > 0 && i < MAX_NET_PROJECTILES) { + if (!deferProjOwner.IsValid()) { + return; + } + + hhProjectile *projectile = SpawnProjectile(); + + projectile->Create(deferProjOwner.GetEntity(), deferProjLaunchOrigin, deferProjLaunchAxis); + projectile->spawnArgs.Set( "weapontype", GetSelf()->spawnArgs.GetString("ddaname", "") ); + projectile->Launch(deferProjLaunchOrigin, DetermineProjectileAxis(deferProjLaunchAxis), deferProjPushVelocity, 0.0f, 1.0f ); + + deferProjNum--; + i++; + } +} + +/* +================ +hhFireController::LaunchProjectiles +================ +*/ +void hhFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + if (gameLocal.isMultiplayer && numProjectiles > MAX_NET_PROJECTILES && !dict->GetBool("net_noProjectileDefer", "0") && + !dict->GetBool("net_clientProjectiles", "1") && !gameLocal.isClient) { + //HUMANHEAD rww - in mp our gigantic single-snapshot projectile spawns tend to be very destructive toward bandwidth. + //and so, projectile deferring. + deferProjNum = numProjectiles; + deferProjTime = 0; + deferProjLaunchOrigin = launchOrigin; + deferProjLaunchAxis = aimAxis; + deferProjPushVelocity = pushVelocity; + deferProjOwner = projOwner; + } + else { + if (gameLocal.isClient && !gameLocal.isNewFrame) { + return; + } + + hhProjectile* projectile = NULL; + bool clientProjectiles = dict->GetBool("net_clientProjectiles", "1"); + for( int ix = 0; ix < numProjectiles; ++ix ) { + if (clientProjectiles) { //HUMANHEAD rww - clientside projectiles! + projectile = hhProjectile::SpawnClientProjectile( GetProjectileDict() ); + } + else { + projectile = SpawnProjectile(); + } + + projectile->Create( projOwner, launchOrigin, aimAxis ); + + projectile->spawnArgs.Set( "weapontype", GetSelf()->spawnArgs.GetString("ddaname", "") ); + + projectile->Launch( launchOrigin, DetermineProjectileAxis(aimAxis), pushVelocity, 0.0f, 1.0f ); + } + } +} + +/* +================ +hhFireController::AssureInsideCollisionBBox +================ +*/ +idVec3 hhFireController::AssureInsideCollisionBBox( const idVec3& origin, const idMat3& axis, const idBounds& ownerAbsBounds, float projMaxHalfDim ) const { + float distance = 0.0f; + if( !ownerAbsBounds.RayIntersection(origin, -axis[0], distance) ) { + distance = 0.0f; + } + + // HUMANHEAD CJR: If the player is touching a portal, then set the projectile inside the player so it has a chance to collide with the portal + idEntity *owner = GetProjectileOwner(); + if ( owner && owner->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(owner); + if ( player->IsPortalColliding() ) { // Player is touching a portal, so force it inside the player bounds + distance = 2.0f * idMath::Fabs( owner->GetPhysics()->GetBounds()[1].y ); // Push back by the player's size + } + } // HUMANHEAD END + + //Need to come back half the size of the projectiles bbox to + //guarentee that the whole projectile is in the owners bbox + return origin + (distance + projMaxHalfDim) * -axis[0]; +} + +/* +================ +hhFireController::CalculateMuzzlePosition +================ +*/ +void hhFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + origin = GetMuzzlePosition(); + axis = GetSelf()->GetAxis(); + + if( g_showProjectileLaunchPoint.GetBool() ) { + idVec3 v = (origin - GetSelf()->GetOrigin()) * GetSelf()->GetAxis().Transpose(); + gameLocal.Printf( "Launching from: (relative to weapon): %s\n", v.ToString() ); + hhUtils::DebugCross( colorGreen, origin, 5, 5000 ); + gameRenderWorld->DebugLine( colorBlue, GetSelf()->GetOrigin(), GetSelf()->GetOrigin() + (v * GetSelf()->GetAxis()), 5000 ); + } +} + +/* +================ +hhFireController::UpdateMuzzleFlashPosition +================ +*/ +void hhFireController::UpdateMuzzleFlashPosition() { + muzzleFlash.axis = GetSelf()->GetAxis(); + muzzleFlash.origin = AssureInsideCollisionBBox( muzzleOrigin, muzzleFlash.axis, GetProjectileOwner()->GetPhysics()->GetAbsBounds(), projectileMaxHalfDimension ); + + //TEST + /*trace_t trace; + idVec3 flashSize = dict->GetVector( "flashSize" ); + if( gameLocal.clip.TracePoint(trace, muzzleFlash.origin, muzzleFlash.origin + muzzleFlash.axis[0] * flashSize[0], MASK_VISIBILITY, GetSelf()) ) { + flashSize[0] *= trace.fraction; + } + muzzleFlash.lightRadius = flashSize; + muzzleFlash.origin += muzzleFlash.axis[0] * flashSize[0] * 0.5f; + muzzleFlash.lightCenter.Set( flashSize[0] * -0.5f, 0.0f, 0.0f ); + + hhUtils::Swap( muzzleFlash.lightRadius[0], muzzleFlash.lightRadius[2] ); + hhUtils::Swap( muzzleFlash.lightCenter[0], muzzleFlash.lightCenter[2] ); + muzzleFlash.axis = hhUtils::SwapXZ( muzzleFlash.axis );*/ + //TEST + + // put the world muzzle flash on the end of the joint, no matter what + //GetGlobalJointTransform( false, flashJointWorld, muzzleOrigin, muzzleFlash.axis ); +} + +/* +================ +hhFireController::MuzzleFlash +================ +*/ +void hhFireController::MuzzleFlash() { + if (!g_muzzleFlash.GetBool()) { + return; + } + + UpdateMuzzleFlashPosition(); + + if( muzzleFlash.lightRadius[0] < VECTOR_EPSILON || muzzleFlash.lightRadius[1] < VECTOR_EPSILON || muzzleFlash.lightRadius[2] < VECTOR_EPSILON ) { + return; + } + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.GetTime() ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.RandomFloat(); + + //info.worldMuzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.GetTime() ); + //info.worldMuzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ]; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.GetTime() + flashTime; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + //gameRenderWorld->UpdateLightDef( info.worldMuzzleFlashHandle, &info.worldMuzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + //info.worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &info.worldMuzzleFlash ); + } +} + +/* +================ +hhFireController::UpdateMuzzleFlash +================ +*/ +void hhFireController::UpdateMuzzleFlash() { + AttemptToRemoveMuzzleFlash(); + + if( muzzleFlashHandle != -1 ) { + UpdateMuzzleFlashPosition(); + if( muzzleFlash.lightRadius[0] < VECTOR_EPSILON || muzzleFlash.lightRadius[1] < VECTOR_EPSILON || muzzleFlash.lightRadius[2] < VECTOR_EPSILON ) { //rww - added to mimic the behaviour of hhFireController::MuzzleFlash + return; + } + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + //gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); + } +} + +/* +================ +hhFireController::CreateMuzzleFx +================ +*/ +void hhFireController::CreateMuzzleFx( const idVec3& pos, const idMat3& axis ) { + hhFxInfo fxInfo; + + if (!gameLocal.GetLocalPlayer()) { //rww - not at all necessary for ded server + return; + } + if( GetSelf()->IsHidden() || !GetSelf()->GetRenderEntity()->hModel ) { + return; + } + + fxInfo.SetNormal( axis[0] ); + fxInfo.RemoveWhenDone( true ); + fxInfo.SetEntity( GetSelf() ); + + + //GetSelf()->BroadcastFxInfo( dict->GetString("fx_muzzleFlash"), pos, axis, &fxInfo ); + //rww - this is now client-only. + GetSelf()->SpawnFxLocal( dict->GetString("fx_muzzleFlash"), pos, axis, &fxInfo, true ); +} + +/* +================ +hhFireController::Save +================ +*/ +void hhFireController::Save( idSaveGame *savefile ) const { + savefile->WriteDict( dict ); + savefile->WriteFloat( yawSpread ); + savefile->WriteString( projDictName ); + savefile->WriteVec3( muzzleOrigin ); + savefile->WriteMat3( muzzleAxis ); + savefile->WriteRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( muzzleFlashHandle ); + savefile->WriteInt( muzzleFlashEnd ); + savefile->WriteInt( flashTime ); + savefile->WriteInt( ammoRequired ); + savefile->WriteFloat( fireDelay ); + savefile->WriteFloat( spread ); + savefile->WriteBool( bCrosshair ); + savefile->WriteFloat( projectileMaxHalfDimension ); + savefile->WriteInt( numProjectiles ); + //HUMANHEAD PCF mdl 05/04/06 - Save whether the light is active + savefile->WriteBool( muzzleFlashHandle != -1 ); +} + +/* +================ +hhFireController::Restore +================ +*/ +void hhFireController::Restore( idRestoreGame *savefile ) { + savefile->ReadDict( &restoredDict ); + dict = &restoredDict; + savefile->ReadFloat( yawSpread ); + savefile->ReadString( projDictName ); + savefile->ReadVec3( muzzleOrigin ); + savefile->ReadMat3( muzzleAxis ); + savefile->ReadRenderLight( muzzleFlash ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( muzzleFlashHandle ); + savefile->ReadInt( muzzleFlashEnd ); + savefile->ReadInt( flashTime ); + savefile->ReadInt( ammoRequired ); + savefile->ReadFloat( fireDelay ); + savefile->ReadFloat( spread ); + savefile->ReadBool( bCrosshair ); + savefile->ReadFloat( projectileMaxHalfDimension ); + savefile->ReadInt( numProjectiles ); + + //HUMANHEAD PCF mdl 05/04/06 - Restore the light if necessary + bool bLight; + savefile->ReadBool( bLight ); + if ( bLight ) { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } + + SetProjectileDict( projDictName ); +} + +/* +============================== +hhFireController::GetMuzzlePosition +============================== +*/ +idVec3 hhFireController::GetMuzzlePosition() const { + idVec3 muzzle( dict->GetVector("muzzleOffset", "2 0 0") ); + return GetSelfConst()->GetOrigin() + ( muzzle * GetSelfConst()->GetAxis() ); +} + +/* +============================== +hhFireController::GetMuzzlePosition +============================== +*/ +bool hhFireController::CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis) { //rww + return false; +} diff --git a/neo/prey/Prey/prey_firecontroller.h b/neo/prey/Prey/prey_firecontroller.h new file mode 100644 index 00000000..39f5e4ed --- /dev/null +++ b/neo/prey/Prey/prey_firecontroller.h @@ -0,0 +1,160 @@ +#ifndef __HH_FIRE_CONTROLLER_H +#define __HH_FIRE_CONTROLLER_H + +#include "gamesys/Class.h" + +#define MAX_NET_PROJECTILES 3 //rww + +class hhFireController : public idClass { + ABSTRACT_PROTOTYPE(hhFireController) + +public: + hhFireController(); + virtual ~hhFireController(); + + virtual void Clear(); + virtual void Init( const idDict* viewDict ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool UsesCrosshair() const = 0; + virtual idVec3 GetMuzzlePosition() const; + ID_INLINE virtual bool HasAmmo() const; + virtual void UseAmmo() = 0; + virtual int AmmoAvailable() const = 0; + ID_INLINE virtual int AmmoRequired() const; + ID_INLINE float GetFireDelay() const; + + void UpdateMuzzleFlash(); + virtual void CreateMuzzleFx( const idVec3& pos, const idMat3& axis ); + + ID_INLINE virtual idMat3 DetermineProjectileAxis( const idMat3& axis ); + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); + + void MuzzleFlash(); + virtual void WeaponFeedback() = 0; + + virtual //HUMANHEAD bjk + const idDict* GetProjectileDict() const { return projectile; } + + void SetWeaponDict( const idDict *wdict ) { dict = wdict; yawSpread = dict->GetFloat("yawSpread"); } + + void CheckDeferredProjectiles(void); //rww + + virtual bool CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis); //rww +protected: + void SetProjectileDict( const char* name ); + + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); + virtual idEntity* GetProjectileOwner() const = 0; + ID_INLINE virtual hhProjectile* SpawnProjectile(); + + virtual const idBounds& GetCollisionBBox() = 0; + virtual void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); + + ID_INLINE void AttemptToRemoveMuzzleFlash(); + void UpdateMuzzleFlashPosition(); + + idVec3 AssureInsideCollisionBBox( const idVec3& origin, const idMat3& axis, const idBounds& ownerAbsBounds, float projMaxHalfDim ) const; + virtual idMat3 DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ); + + virtual hhRenderEntity *GetSelf() = 0; + virtual const hhRenderEntity *GetSelfConst() const = 0; + +protected: + idDict restoredDict; + const idDict* dict; + + const idDict * projectile; + idStr projDictName; + + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + // muzzle flash + renderLight_t muzzleFlash; // positioned on view weapon bone + int muzzleFlashHandle; + + int muzzleFlashEnd; + int flashTime; + + // ammo management + int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + + float fireDelay; + float spread; + + float yawSpread; //rww - extra spread on yaw + + bool bCrosshair; + + float projectileMaxHalfDimension; + + int numProjectiles; + + //rww - projectile deferring + int deferProjNum; + int deferProjTime; + idVec3 deferProjLaunchOrigin; + idMat3 deferProjLaunchAxis; + idVec3 deferProjPushVelocity; + idEntityPtr deferProjOwner; +}; + +/* +================= +hhFireController::DetermineAimAxis +================= +*/ +ID_INLINE idMat3 hhFireController::DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ) { + return weaponAxis; +} + +/* +================ +hhFireController::HasAmmo +================ +*/ +ID_INLINE bool hhFireController::HasAmmo() const { + return AmmoAvailable() != 0; +} + +/* +================ +hhFireController::SpawnProjectile +================ +*/ +ID_INLINE hhProjectile* hhFireController::SpawnProjectile() { + return hhProjectile::SpawnProjectile( GetProjectileDict() ); +} + +/* +================ +hhFireController::AttemptToRemoveMuzzleFlash +================ +*/ +ID_INLINE void hhFireController::AttemptToRemoveMuzzleFlash() { + if( gameLocal.GetTime() >= muzzleFlashEnd ) { + SAFE_FREELIGHT( muzzleFlashHandle ); + } +} + +/* +================ +hhFireController::AmmoRequired +================ +*/ +ID_INLINE int hhFireController::AmmoRequired() const { + return ammoRequired; +} + +/* +================ +hhFireController::GetFireDelay +================ +*/ +ID_INLINE float hhFireController::GetFireDelay() const { + return fireDelay; +} + +#endif diff --git a/neo/prey/Prey/prey_game.cpp b/neo/prey/Prey/prey_game.cpp new file mode 100644 index 00000000..7738f7e5 --- /dev/null +++ b/neo/prey/Prey/prey_game.cpp @@ -0,0 +1,1438 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" +//#include "../prey/win32/fxdlg.h" + +extern idCVar com_forceGenericSIMD; + +//HUMANHEAD: aob - needed for networking to send the least amount of bits +const int DECL_MAX_TYPES_NUM_BITS = hhMath::BitsForInteger( DECL_MAX_TYPES ); +//HUMANHEAD END + +//============================================================================= +// Overridden functions +//============================================================================= + +//--------------------------------------------------- +// +// hhGameLocal::Init +// +//--------------------------------------------------- +void hhGameLocal::Init( void ) { + + //HUMANHEAD rww - moved into idGameLocal::Init, this should be done after the managed heap is initialized. + //ddaManager = new hhDDAManager; //must be before calling idGameLocal::Init(), otherwise if the map can't load, will cause a crash. + //HUMANHEAD END + + idGameLocal::Init(); + + dwWorldClipModel = NULL; +#if _HH_INLINED_PROC_CLIPMODELS + inlinedProcClipModels.Clear(); //HUMANHEAD rww +#endif + + sunCorona = NULL; // CJR + lastAIAlertRadius = 0; + + P_InitConsoleCommands(); + + //HUMANHEAD rww - check lglcd validity and reset values + logitechLCDEnabled = sys->LGLCD_Valid(); + logitechLCDDisplayAlt = false; + logitechLCDButtonsLast = 0; + logitechLCDUpdateTime = 0; + //HUMANHEAD END +} + +//--------------------------------------------------- +// +// hhGameLocal::Shutdown +// +//--------------------------------------------------- +void hhGameLocal::Shutdown( void ) { + +#if INGAME_DEBUGGER_ENABLED + debugger.Shutdown(); // HUMANHEAD pdm: Shut down the debugger before idDict gets shut down +#endif + + idGameLocal::Shutdown(); + + //HUMANHEAD rww - this must be done before managed heap destruction to correspond + //delete ddaManager; + //ddaManager = NULL; + //HUMANHEAD END +} + + +//--------------------------------------------------- +// +// hhGameLocal::UnregisterEntity +// +//--------------------------------------------------- +void hhGameLocal::UnregisterEntity( idEntity *ent ) { + assert( ent ); + + if ( talonTargets.Find( ent ) ) { + talonTargets.Remove( ent ); + } + + idGameLocal::UnregisterEntity( ent ); +} + +//--------------------------------------------------- +// +// hhGameLocal::MapShutdown +// +//--------------------------------------------------- +void hhGameLocal::MapShutdown( void ) { + //HUMANHEAD: mdc - added support for automatically dumping stats on map switch/game exit + if( !isMultiplayer && gamestate != GAMESTATE_NOMAP && g_dumpDDA.GetBool() ) { //make sure we actually have a valid map to export + GetDDA()->Export(NULL); //export the stats + } + //Always clear our tracking stats and create a default node + GetDDA()->ClearTracking(); //Clear Tracking statistics +// GetDDA()->CreateDefaultSectionNode(); //Setup our default section-node + + idGameLocal::MapShutdown(); + + sunCorona = NULL; + + talonTargets.Clear(); +} + +//--------------------------------------------------- +// +// hhGameLocal::InitFromNewMap +// +//--------------------------------------------------- +void hhGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ) { + + //HUMANHEAD rww - throw up the prey logo if using the logitech lcd screen + if (logitechLCDEnabled) { + sys->LGLCD_UploadImage(NULL, -1, -1, false, true); + } + //HUMANHEAD END + + talonTargets.Clear(); // CJR: Must be before idGameLocal::InitFromNewMap() + +#if INGAME_DEBUGGER_ENABLED + debugger.Reset(); +#endif + + hands.Clear(); + + idGameLocal::InitFromNewMap(mapName, renderWorld, soundWorld, isServer, isClient, randseed); + + // CJR: Determine if this map is a LOTA map by looking for a special entity on the map + bIsLOTA = false; + if ( FindEntity( "LOTA_ThisMapIsLOTA" ) ) { + bIsLOTA = true; + } +} + +//============================================================================= +// +// hhGameLocal::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 +// +// HUMANHEAD pdm: Override for doing our own precaching +//============================================================================= +void hhGameLocal::CacheDictionaryMedia( const idDict *dict ) { +/* HUMANHEAD mdc - fix so com_makingBuild stuff will work correctly... (this was causing too early of an out) + if ( dict == NULL ) { + return; + } +*/ + const idKeyValue *kv = NULL; + const idDecl *decl = NULL; + idFile *file = NULL; + idStr fxName; + idStr buffer; + + idGameLocal::CacheDictionaryMedia( dict ); + + if( dict == NULL ) { //mdc - added early out + return; + } +//HUMANHEAD mdc - skip caching if we are in development and not making a build + if( !g_precache.GetBool() && !sys_forceCache.GetBool() && !cvarSystem->GetCVarBool("com_makingBuild") ) { + return; + } +//HUMANHEAD END + + + // TEMP: precache clip models until they become model_clip + kv = dict->MatchPrefix( "clipmodel", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + declManager->MediaPrint( "Precaching clipmodel: %s\n", kv->GetValue().c_str() ); + renderModelManager->FindModel( kv->GetValue() ); + } + kv = dict->MatchPrefix( "clipmodel", kv ); + } + + //HUMANHEAD bjk: removed smoke_wound_. now using fx_wound and is cached in fx_. no seperate handling needed + + kv = dict->MatchPrefix( "beam", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + if (kv->GetValue().Find(".beam") >= 0) { + declManager->MediaPrint( "Precaching beam %s\n", kv->GetValue().c_str() ); + declManager->FindType(DECL_BEAM, kv->GetValue(), false); // Cache the beam decl + renderModelManager->FindModel(kv->GetValue()); // Ensure the beam model is cached as well, since beams are a combination of a decl and a rendermodel + } + else { // Must be a reference to a beam entity + declManager->MediaPrint( "Precaching beam entityDef %s\n", kv->GetValue().c_str() ); + FindEntityDef( kv->GetValue().c_str(), false ); + } + } + kv = dict->MatchPrefix( "beam", kv ); + } +} + + +//============================================================================= +// New functionality +//============================================================================= + +//============================================================================= +// +// hhGameLocal::SpawnAppendedMapEntities +// +// HUMANHEAD pdm: Support for level appending +//============================================================================= +#if DEATHWALK_AUTOLOAD +void hhGameLocal::SpawnAppendedMapEntities() { + if (additionalMapFile) { + // Swap mapFile and additionalMapFile + idMapFile *mainMapFile = mapFile; + mapFile = additionalMapFile; + + // This routine based largely on idGameLocal::SpawnMapEntities + + // parse the key/value pairs and spawn entities for dw + // From SpawnMapEntities(); + idMapEntity *mapEnt; + int numEntities; + idDict args; + + numEntities = mapFile->GetNumEntities(); + if ( numEntities == 0 ) { + Error( "...no entities" ); + } + + for ( int i = 1 ; i < numEntities ; i++ ) { // skip worldspawn + mapEnt = mapFile->GetEntity( i ); + args = mapEnt->epairs; + + if ( !InhibitEntitySpawn( args ) ) { + // precache any media specified in the map entity + gameLocal.CacheDictionaryMedia( &args ); + + //HUMANHEAD rww - ok on client + SpawnEntityDef( args, NULL, true, false, true ); + } + } + + // Restore main mapfile + delete additionalMapFile; + additionalMapFile = NULL; + mapFile = mainMapFile; + + + // Since the world collision is always collision model 0, link our model in like an entity would + dwWorldClipModel = new idClipModel("dw_worldmap"); + dwWorldClipModel->SetContents(CONTENTS_SOLID); + dwWorldClipModel->SetEntity(world); + dwWorldClipModel->Link(clip); + } +} +#endif + +//============================================================================= +// +// hhGameLocal::RegisterTalonTarget +// +//============================================================================= +void hhGameLocal::RegisterTalonTarget( idEntity *ent ) { + talonTargets.Append(ent); +} + +//============================================================================= +// +// hhGameLocal::SpawnClientObject +// rww - meant for spawning client ents only. +//============================================================================= +idEntity* hhGameLocal::SpawnClientObject( const char* objectName, idDict* additionalArgs ) { + idDict localArgs; + idDict* args = NULL; + idEntity* object = NULL; + bool clientEnt = true; + + if (!gameLocal.isClient) { //if it's the server spawning a client entity we want to spawn it for listen servers but keep it local + clientEnt = false; + } + + if( !objectName || !objectName[0]) { + Error( "hhGameLocal::SpawnObject: Invalid object name\n" ); + } + + args = ( additionalArgs != NULL ) ? additionalArgs : &localArgs; + + args->Set( "classname", objectName ); + if( !SpawnEntityDef( *args, &object, true, clientEnt ) ) { + Error( "hhGameLocal::SpawnObject: Failed to spawn %s\n", objectName ); + } + + if (object) { + object->fl.clientEntity = clientEnt; + object->fl.networkSync = false; + } + + return object; +} + +//============================================================================= +// +// hhGameLocal::SpawnObject +// +//============================================================================= +idEntity* hhGameLocal::SpawnObject( const char* objectName, idDict* additionalArgs ) { + idDict localArgs; + idDict* args = NULL; + idEntity* object = NULL; + + //rww - are you hitting this? then the code it's coming from is doing something it shouldn't be. + //the client should never, ever call SpawnObject. + assert(!gameLocal.isClient); + + if( !objectName || !objectName[0]) { + Error( "hhGameLocal::SpawnObject: Invalid object name\n" ); + } + + args = ( additionalArgs != NULL ) ? additionalArgs : &localArgs; + + args->Set( "classname", objectName ); + if( !SpawnEntityDef( *args, &object ) ) { + Error( "hhGameLocal::SpawnObject: Failed to spawn %s\n", objectName ); + } + + return object; +} + + +// +// hhGameLocal::RadiusDamage() +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +// +void hhGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower ) { + float distSquared, radiusSquared, damageScale, attackerDamageScale, attackerPushScale; + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + idVec3 v, damagePoint, dir; + int i, e, damage, radius, push; + // HUMANHEAD AOB + int pushRadius; + trace_t result; + // HUMANHEAD END + + const idDict *damageDef = FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + damageDef->GetInt( "damage", "0", damage ); // HUMANHEAD JRM - changed to ZERO not 20 default + damageDef->GetInt( "radius", "50", radius ); + damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); + damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); + damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + + // HUMANHEAD aob + pushRadius = Max( 1.0f, damageDef->GetFloat("push_radius", va("%d", radius)) ); + if ( damageDef->GetBool( "nopush" ) ) { + push = 0; + } + // HUMANHEAD END + + if ( radius < 1 ) { + radius = 1; + } + radiusSquared = radius*radius; + + bounds = idBounds( origin ).Expand( radius ); + + // get all entities touching the bounds + numListedEntities = clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + + if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { + inflictor = static_cast(inflictor)->GetBody(); + } + if ( attacker && attacker->IsType( idAFAttachment::Type ) ) { + attacker = static_cast(attacker)->GetBody(); + } + if ( ignoreDamage && ignoreDamage->IsType( idAFAttachment::Type ) ) { + ignoreDamage = static_cast(ignoreDamage)->GetBody(); + } + + // apply damage to the entities + if( damage > 0 ) { // HUMANHEAD JRM - only do damage if WE HAVE damage + for ( e = 0; e < numListedEntities; e++ ) { + ent = entityList[ e ]; + assert( ent ); + + if ( !ent->fl.takedamage ) { + continue; + } + + if ( ent == inflictor /*|| ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor )*/ ) { + continue; + } + + if ( ent == ignoreDamage /*|| ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignoreDamage )*/ ) { + continue; + } + + if ( ent->IsType( idAFAttachment::Type ) ) { // bjk: no double splash damage from heads + continue; + } + + // don't damage a dead player + if ( isMultiplayer && ent->entityNumber < MAX_CLIENTS && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >( ent )->health < 0 ) { + continue; + } + + // find the distance from the edge of the bounding box + for ( i = 0; i < 3; i++ ) { + if ( origin[ i ] < ent->GetPhysics()->GetAbsBounds()[0][ i ] ) { + v[ i ] = ent->GetPhysics()->GetAbsBounds()[0][ i ] - origin[ i ]; + } else if ( origin[ i ] > ent->GetPhysics()->GetAbsBounds()[1][ i ] ) { + v[ i ] = origin[ i ] - ent->GetPhysics()->GetAbsBounds()[1][ i ]; + } else { + v[ i ] = 0; + } + } + + distSquared = v.LengthSqr(); + if ( distSquared >= radiusSquared ) { + continue; + } + + //HUMANHEAD: aob - see if radius damage is blocked. CanDamage is used by more than just RadiusDamage + clip.TracePoint( result, origin, (ent->GetPhysics()->GetAbsBounds()[0] + ent->GetPhysics()->GetAbsBounds()[1]) * 0.5f, CONTENTS_BLOCK_RADIUSDAMAGE, ignoreDamage ); + if( result.fraction < 1.0f && result.c.entityNum != ent->entityNumber ) { + continue; + } + //HUMANHEAD END + + bool canDamage = true; + if ( GERMAN_VERSION || g_nogore.GetBool() ) { + if ( ent->IsType(idActor::Type) ) { + idActor *actor = reinterpret_cast< idActor * > ( ent ); + if ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) { + canDamage = false; + } + } + } + + + if ( canDamage && ent->CanDamage( origin, damagePoint ) ) { + // push the center of mass higher than the origin so players + // get knocked into the air more + dir = ent->GetPhysics()->GetOrigin() - origin; + dir[ 2 ] += 24; + + // get the damage scale + damageScale = dmgPower * ( 1.0f - (distSquared / radiusSquared) ); + if ( ent == attacker || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == attacker ) ) { + damageScale *= attackerDamageScale; + } + + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, INVALID_JOINT ); + } + } + } // HUMANHEAD END + + // push physics objects + if ( push ) { + //HUMANHEAD: aob - changed radius to pushRadius + RadiusPush( origin, pushRadius, push * dmgPower, attacker, ignorePush, attackerPushScale, false ); + } +} + +/* +============== +hhGameLocal::RadiusPush + PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +============== +*/ +void hhGameLocal::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; + trace_t trace; //HUMANHEAD: aob + + dir.Set( 0.0f, 0.0f, 1.0f ); + + bounds = idBounds( origin ).Expand( radius ); + + // get all clip models touching the bounds + numListedClipModels = clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + if ( inflictor && inflictor->IsType( idAFAttachment::Type ) ) { + inflictor = static_cast(inflictor)->GetBody(); + } + if ( ignore && ignore->IsType( idAFAttachment::Type ) ) { + ignore = static_cast(ignore)->GetBody(); + } + + // 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(); + + // never push projectiles + if ( ent->IsType( idProjectile::Type ) ) { + continue; + } + + // players use "knockback" in idPlayer::Damage + if ( ent->IsType( idPlayer::Type ) && !quake ) { + continue; + } + + // don't push the ignore entity + if ( ent == ignore || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == ignore ) ) { + continue; + } + + if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetAbsBounds().GetCenter() ) ) { + continue; + } + + // Don't affect ragdolls in non-gore mode + if ( GERMAN_VERSION || g_nogore.GetBool() ) { + if ( ent->IsType(idActor::Type) ) { + idActor *actor = reinterpret_cast< idActor * > ( ent ); + if ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) { + continue; + } + } + } + + // scale the push for the inflictor + if ( ent == inflictor || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody() == inflictor ) ) { + scale = inflictorScale; + } else if ( ent->IsType(hhMoveable::Type) ) { + scale = ent->spawnArgs.GetFloat("radiusPush", "4.0"); + } else if( !ent->IsType(idActor::Type) ) { + scale = 4.0f; + } else { + scale = 1.0f; + } + + //HUMANHEAD: aob - see if radius damage is blocked + clip.TracePoint( trace, origin, clipModel->GetEntity()->GetOrigin(), CONTENTS_BLOCK_RADIUSDAMAGE, ignore ); + if( trace.fraction < 1.0f && trace.c.entityNum != clipModel->GetEntity()->entityNumber ) { + continue; + } + //HUMANHEAD END + + if ( quake ) { + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); + } else { + //RadiusPushClipModel( origin, scale * push, clipModel ); + + //HUMANHEAD bjk + idVec3 impulse = clipModel->GetAbsBounds().GetCenter() - origin; + float dist = impulse.Normalize() / radius; + impulse.z += 1.0f; + dist = 0.6f - 0.3f*dist; + impulse *= push * dist * scale; + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), impulse ); + //HUMANHEAD END + } + } +} + +//HUMANHEAD rww +void hhGameLocal::LogitechLCDUpdate(void) { + hhPlayer *pl = (hhPlayer *)GetLocalPlayer(); + if (!pl) { + return; + } + + DWORD buttons; + sys->LGLCD_ReadSoftButtons(&buttons); + if (logitechLCDButtonsLast != buttons && buttons) { + logitechLCDDisplayAlt = !logitechLCDDisplayAlt; + } + logitechLCDButtonsLast = buttons; + + if (logitechLCDUpdateTime >= gameLocal.time) { //only update the screen at 20fps + return; + } + logitechLCDUpdateTime = gameLocal.time + 50; + + sys->LGLCD_DrawBegin(); + if (logitechLCDDisplayAlt) { //primary/secondary ammo + char *ammoStr = va("%s%s", common->GetLanguageDict()->GetString("#str_41150"), common->GetLanguageDict()->GetString("#str_41152")); //"Ammo: N/A"; + char *ammoAltStr = va("%s%s", common->GetLanguageDict()->GetString("#str_41151"), common->GetLanguageDict()->GetString("#str_41152")); //"Alt. Ammo: N/A"; + if (pl->weapon.IsValid()) { + int a = pl->weapon->AmmoAvailable(); + if (a >= 0) { + if (a > 999) { + a = 999; + } + ammoStr = va("%s%i", common->GetLanguageDict()->GetString("#str_41150"), a); //"Ammo: %i" + } + if (pl->weapon->GetAmmoType() != pl->weapon->GetAltAmmoType()) { + a = pl->weapon->AltAmmoAvailable(); + if (a >= 0) { + if (a > 999) { + a = 999; + } + ammoAltStr = va("%s%i", common->GetLanguageDict()->GetString("#str_41151"), a); //"Alt. Ammo: %i" + } + } + } + + sys->LGLCD_DrawText(ammoStr, 46, 6, true); + sys->LGLCD_DrawText(ammoAltStr, 46, 22, true); + + //draw the weapon icon (disabled for now, since it isn't very..recognizable) + //sys->LGLCD_DrawShape(3, 54, 0, 0, 0, 0, true); + } + else { //health/spirit + int v; + v = pl->health; + if (v > 999) { + v = 999; + } + else if (v < 0) { + v = 0; + } + sys->LGLCD_DrawText(va("%s%i", common->GetLanguageDict()->GetString("#str_41153"), v), 70, 6, true); //"Health: %i" + v = pl->GetSpiritPower(); + if (v > 999) { + v = 999; + } + else if (v < 0) { + v = 0; + } + sys->LGLCD_DrawText(va("%s%i", common->GetLanguageDict()->GetString("#str_41154"), v), 70, 22, true); //"Spirit: %i" + + //draw the tommy and talon + float eyePitch; + if (pl->InVehicle()) { + eyePitch = 0.0f; + } + else { + eyePitch = pl->GetEyeAxis()[2].ToAngles().pitch+90.0f; + if (eyePitch < 1.0f && eyePitch > -1.0f) { + eyePitch = 0.0f; + } + } + sys->LGLCD_DrawShape(2, 42, 0, 0, 0, (int)eyePitch, true); + } + + //draw the compass + sys->LGLCD_DrawShape(0, 0, 0, 0, 0, 0, false); //base + sys->LGLCD_DrawShape(1, 21, 21, 13, 1, (int)-pl->GetViewAngles().yaw, true); //line + sys->LGLCD_DrawFinish(false); +} +//HUMANHEAD END + +// +// RunFrame() +// +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +gameReturn_t hhGameLocal::RunFrame( const usercmd_t *clientCmds ) { + idEntity * ent; + int num; + float ms; + idTimer timer_think, timer_events, timer_singlethink; + gameReturn_t ret; + idPlayer *player; + const renderView_t *view; + // HUMANHEAD pdm + idTimer timer_singledormant; + bool dormant; + const float thinkalpha = 0.98f; // filter with historical timings + // HUMANHEAD END + + ret.sessionCommand[0] = 0; + +#ifdef _DEBUG + if ( isMultiplayer ) { + assert( !isClient ); + } +#endif + + player = GetLocalPlayer(); + + if ( !isMultiplayer && g_stopTime.GetBool() ) { + // clear any debug lines from a previous frame + gameRenderWorld->DebugClearLines( time + 1 ); + + // set the user commands for this frame + memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) ); + + // Fake these categories so it still displays any that are in the history when time is stopped + { PROFILE_SCOPE("Misc_Think", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Dormant Tests", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Sound", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Animation", PROFMASK_NORMAL); } + { PROFILE_SCOPE("Scripting", PROFMASK_NORMAL); } + + if ( player ) { + player->Think(); + if ( player->InVehicle() && player->GetVehicleInterface() ) { + if ( player->GetVehicleInterface()->GetVehicle() ) { + player->GetVehicleInterface()->GetVehicle()->Think(); + } + } + } + } else do { + // update the game time + framenum++; + previousTime = time; + time += msec; + realClientTime = time; + timeRandom = time; //HUMANHEAD rww + +#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 ); + } + } + + // clear any debug lines from a previous frame + gameRenderWorld->DebugClearLines( time ); + + // clear any debug polygons from a previous frame + gameRenderWorld->DebugClearPolygons( time ); + + // set the user commands for this frame + memcpy( usercmds, clientCmds, numClients * sizeof( usercmds[ 0 ] ) ); + + // free old smoke particles + smokeParticles->FreeSmokes(); + + // process events on the server + ServerProcessEntityNetworkEventQueue(); + + // update our gravity vector if needed. + UpdateGravity(); + + // create a merged pvs for all players + SetupPlayerPVS(); + + // sort the active entity list + SortActiveEntityList(); + + timer_think.Clear(); + timer_think.Start(); + PROFILE_START("Misc_Think", PROFMASK_NORMAL); // HUMANHEAD pdm + + // HUMANHEAD pdm: This loop reworked to support debugger and dormant timings + // let entities think + if ( g_timeentities.GetFloat() || g_debugger.GetInteger() || g_dormanttests.GetBool()) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + dormant = false; + timer_singledormant.Clear(); + if (!ent->fl.neverDormant) { + timer_singledormant.Start(); + dormant = ent->CheckDormant(); + timer_singledormant.Stop(); + } + + timer_singlethink.Clear(); + if( !dormant ) { + timer_singlethink.Start(); + ent->Think(); + timer_singlethink.Stop(); + } + + ms = timer_singlethink.Milliseconds(); + if ( g_timeentities.GetFloat() && ms >= g_timeentities.GetFloat() ) { + gameLocal.Printf( "%d: entity '%s': %.1f ms\n", time, ent->name.c_str(), ms ); + } + if (g_debugger.GetInteger() || g_dormanttests.GetBool()) { + ent->thinkMS = ent->thinkMS*thinkalpha + (1.0-thinkalpha)*ms; + } + if (g_dormanttests.GetBool() && !ent->fl.neverDormant) { + float msDormant = timer_singledormant.Milliseconds(); + ent->dormantMS = ent->dormantMS*thinkalpha + (1.0f-thinkalpha)*msDormant; + + if (ent->dormantMS > ent->thinkMS && !dormant) { + // If we're spending more time on dormant checks than on actually thinking, + // turn dormant checks off on that class. + Printf("%30s Dms=%6.4f Tms=%6.4f\n", ent->GetClassname(), ent->dormantMS, ent->thinkMS); + } + } + 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; + } + // HUMANHEAD JRM + if( !ent->CheckDormant() ) { + ent->Think(); + } + num++; + } + } else { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + // HUMANHEAD JRM + if( !ent->CheckDormant() ) { + ent->Think(); + } + 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; + } + + PROFILE_STOP("Misc_Think", PROFMASK_NORMAL); + + timer_think.Stop(); + timer_events.Clear(); + timer_events.Start(); + + // service any pending events + idEvent::ServiceEvents(); + + timer_events.Stop(); + + // free the player pvs + FreePlayerPVS(); + + // do multiplayer related stuff + if ( isMultiplayer ) { + mpGame.Run(); + } + + // 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; //player->heartRate; // HUMANHEAD pdm: not used + ret.stamina = idMath::FtoiFast( player->stamina ); + // 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 ) ); + 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; + } + } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); + + ret.syncNextGameFrame = skipCinematic; + if ( skipCinematic ) { + soundSystem->SetMute( false ); + skipCinematic = false; + } + + // show any debug info for this frame + RunDebugInfo(); + D_DrawDebugLines(); + + //HUMANHEAD rww + if (logitechLCDEnabled) { + PROFILE_START("LogitechLCDUpdate", PROFMASK_NORMAL); + LogitechLCDUpdate(); + PROFILE_STOP("LogitechLCDUpdate", PROFMASK_NORMAL); + } + //HUMANHEAD END + + return ret; +} + +// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge +bool hhGameLocal::Draw( int clientNum ) { + //HUMANHEAD rww - dedicated server update + if (clientNum == -1) { + gameSoundWorld->PlaceListener( vec3_origin, mat3_identity, -1, gameLocal.time, "Undefined" ); + return false; + } + //HUMANHEAD END + + if ( isMultiplayer ) { + return mpGame.Draw( clientNum ); + } + + //HUMANHEAD: aob - changed idPlayer to hhPlayer + hhPlayer *player = static_cast(entities[ clientNum ]); + + if ( !player ) { + return false; + } + + // render the scene + // HUMANHEAD pdm: added case of vehicle determining the player hud + if (player->InVehicle()) { + player->playerView.RenderPlayerView( player->GetVehicleInterfaceLocal()->GetHUD() ); + } + else { + player->playerView.RenderPlayerView( player->hud ); + } + // HUMANHEAD END + +#if INGAME_DEBUGGER_ENABLED // HUMANHEAD pdm + debugger.UpdateDebugger(); +#endif + + return true; +} + + +float hhGameLocal::GetTimeScale() const { + return hhMath::ClampFloat( VECTOR_EPSILON, 10.0f, cvarSystem->GetCVarFloat("timescale") ); +} + +// +// SimpleMonstersWithinRadius() +// TODO: Use this exclusively and remove MonstersWithinRadius when old AI is removed +int hhGameLocal::SimpleMonstersWithinRadius( const idVec3 org, float radius, idAI **monstList, int maxCount ) const { + idBounds bo( org ); + int entCount = 0; + + bo.ExpandSelf( radius ); + for(int i=0;iGetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { + monstList[entCount++] = hhMonsterAI::allSimpleMonsters[i]; + } + } + + return entCount; +} + +// +// SpawnMapEntities() +// +void hhGameLocal::SpawnMapEntities( void ) { + if ( reactionHandler ) { + delete reactionHandler; + } + reactionHandler = new hhReactionHandler; + idGameLocal::SpawnMapEntities(); +} + +/* +============== +hhGameLocal::SendMessageAI +============== +*/ +void hhGameLocal::SendMessageAI( const idEntity* entity, const idVec3& origin, float radius, const idEventDef& message ) { + idAI *simplemonsters[ MAX_GENTITIES ]; + + if( radius < VECTOR_EPSILON || isClient ) { + return; + } + for( int i = gameLocal.SimpleMonstersWithinRadius(origin, radius, simplemonsters) - 1; i >= 0; --i ) { + if( simplemonsters[i]->GetHealth() > 0 && simplemonsters[i]->IsActive() && !simplemonsters[i]->IsHidden() ) { + simplemonsters[i]->ProcessEvent( &message, entity ); + } + } +} + +/* +============== +hhGameLocal::MatterTypeToMatterName +============== +*/ +const char* hhGameLocal::MatterTypeToMatterName( surfTypes_t type ) const { + return sufaceTypeNames[ type ]; +} + +/* +============== +hhGameLocal::MatterTypeToMatterKey +============== +*/ +const char* hhGameLocal::MatterTypeToMatterKey( const char* prefix, surfTypes_t type ) const { + return va( "%s_%s", prefix, MatterTypeToMatterName(type) ); +} + +/* +============== +hhGameLocal::MatterNameToMatterType +============== +*/ +surfTypes_t hhGameLocal::MatterNameToMatterType( const char* name ) const { + surfTypes_t type = SURFTYPE_NONE; + + for( int ix = 0; ix < MAX_SURFACE_TYPES; ++ix ) { + type = (surfTypes_t)ix; + if( idStr::Icmp(name, MatterTypeToMatterName(type)) ) { + continue; + } + + return type; + } + + return SURFTYPE_NONE; +} + +/* +============== +hhGameLocal::GetMatterType +============== +*/ +surfTypes_t hhGameLocal::GetMatterType( const trace_t& trace, const char* descriptor ) const { + return GetMatterType( gameLocal.GetTraceEntity(trace), trace.c.material, descriptor ); +} + +/* +============== +hhGameLocal::GetMatterType +============== +*/ +surfTypes_t hhGameLocal::GetMatterType( const idEntity *ent, const idMaterial *material, const char* descriptor ) const { + surfTypes_t type = SURFTYPE_NONE; + const char *matterName = NULL; + const idMaterial *remapped = material; + + // If the entityDef has a matter specified, always use it + // Otherwise, use the materials matter. If none, default to metal. + if( ent && ent->spawnArgs.GetString("matter", NULL, &matterName ) ) { + type = MatterNameToMatterType( matterName ); + } + else { + if (ent && ent->GetSkin()) { + remapped = ent->GetSkin()->RemapShaderBySkin(material); + } + type = (remapped != NULL) ? remapped->GetSurfaceType() : SURFTYPE_METAL; + } + + // OBS: Will never happen + if( !type ) { +#if 0 + Warning("No matter for hit surface"); + Warning(" Entity: %s", ent ? ent->name.c_str() : "none"); + Warning(" Class: %s", ent ? ent->GetClassname() : "none"); + Warning(" Material: %s", material ? material->GetName() : "none"); +#endif + type = SURFTYPE_METAL; + } + + if( g_debugMatter.GetInteger() > 0 && descriptor && descriptor[0] ) { + Printf("%s: [%s] ent=[%s] mat=[%s]\n", + descriptor, + MatterTypeToMatterName(type), + ent ? ent->GetName() : "none", + material ? material->GetName() : "none"); + } + + return type; +} + +void hhGameLocal::AlertAI( idEntity *ent ) { + if ( ent && ent->IsType( idActor::Type ) ) { + // alert them for the next frame + lastAIAlertTime = time + msec; + lastAIAlertEntity = static_cast( ent ); + lastAIAlertRadius = 0; //no radius required by default + } +} + +void hhGameLocal::AlertAI( idEntity *ent, float radius ) { + if ( ent && ent->IsType( idActor::Type ) ) { + // alert them for the next frame + lastAIAlertTime = time + msec; + lastAIAlertEntity = static_cast( ent ); + lastAIAlertRadius = radius; //radius of effect + } +} + +//================ +//hhGameLocal::Save +//================ +void hhGameLocal::Save( idSaveGame *savefile ) const { + int i, num = talonTargets.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteObject( talonTargets[i] ); + } + + reactionHandler->Save( savefile ); + savefile->WriteClipModel( dwWorldClipModel ); + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + savefile->WriteInt(inlinedProcClipModels.Num()); + for (i = 0; i < inlinedProcClipModels.Num(); i++) { + savefile->WriteClipModel(inlinedProcClipModels[i]); + } +#endif + //HUMANHEAD END + savefile->WriteVec3( gravityNormal ); + savefile->WriteObject( sunCorona ); + ddaManager->Save( savefile ); + savefile->WriteBool( bIsLOTA ); + savefile->WriteFloat( lastAIAlertRadius ); + + num = staticClipModels.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteClipModel( staticClipModels[i] ); + } + + num = staticRenderEntities.Num(); + savefile->WriteInt( num ); + for( i = 0; i < num; i++ ) { + savefile->WriteRenderEntity( *staticRenderEntities[i] ); + } + + savefile->WriteInt( hands.Num() ); + for ( i = 0; i < hands.Num(); i++ ) { + hands[i].Save( savefile ); + } +} + +//================ +//hhGameLocal::Restore +//================ +void hhGameLocal::Restore( idRestoreGame *savefile ) { + idEntity *ent; + int i, num; + idClipModel *model; + renderEntity_t *renderEnt; + savefile->ReadInt( num ); + talonTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast ( ent ) ); + talonTargets[i] = ent; + } + + reactionHandler = new hhReactionHandler; + reactionHandler->Restore( savefile ); + savefile->ReadClipModel( dwWorldClipModel ); + //HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS + int numInlinedProcClipModels; + savefile->ReadInt(numInlinedProcClipModels); + for (i = 0; i < numInlinedProcClipModels; i++) { + savefile->ReadClipModel(inlinedProcClipModels[i]); + } +#endif + //HUMANHEAD END + savefile->ReadVec3( gravityNormal ); + savefile->ReadObject( reinterpret_cast ( sunCorona ) ); + ddaManager->Restore ( savefile ); + savefile->ReadBool( bIsLOTA ); + savefile->ReadFloat( lastAIAlertRadius ); + + savefile->ReadInt( num ); + staticClipModels.DeleteContents( false ); + staticClipModels.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadClipModel( model ); + staticClipModels[i] = model; + } + + savefile->ReadInt( num ); + staticRenderEntities.DeleteContents( false ); + staticRenderEntities.SetNum( num ); + for( i = 0; i < num; i++ ) { + renderEnt = new renderEntity_t; + savefile->ReadRenderEntity( *renderEnt ); + gameRenderWorld->AddEntityDef( renderEnt ); + staticRenderEntities[i] = renderEnt; + } + + savefile->ReadInt( num ); + hands.SetNum( num ); + for ( i = 0; i < num; i++ ) { + hands[i].Restore( savefile ); + } +} + +bool hhGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { + const char *modelName = spawnArgs.GetString( "model" ); + int inlineEnt = spawnArgs.GetInt( "inline" ); + + if( idStr::Icmp( spawnArgs.GetString( "classname", NULL ), "func_static" ) == 0 && // Only deal with func_static objects + !spawnArgs.GetBool( "neverInline", "0" ) && // and we're not flagged neverInline + ( inlineEnt != 0 || // and we're flagged explicit inline + world->spawnArgs.GetBool( "inlineAllStatics" ) ) ) { // or we're inlining all statics + +#if _HH_INLINED_PROC_CLIPMODELS + if (inlineEnt != 3) { //HUMANHEAD rww +#endif + // Handle explicit clip models + idClipModel *model = NULL; + const char *temp = spawnArgs.GetString( "clipmodel", NULL ); + + if( temp ) { + if ( idClipModel::CheckModel( temp ) ) { + model = new idClipModel( temp ); + } + } + + if( model || !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + // Get the origin and axis of the object + idMat3 axis; + // 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 ) ) { + float angle = spawnArgs.GetFloat( "angle" ); + if ( angle != 0.0f ) { + axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } else { + axis.Identity(); + } + } + + idVec3 origin = spawnArgs.GetVector( "origin" ); + + // Create a clip model for the static object and position it correctly + if( !model ) { + model = new idClipModel( spawnArgs.GetString( "model" ) ); + } + if( spawnArgs.GetBool( "bulletsOnly", "0" ) ) { + model->SetContents( CONTENTS_SHOOTABLE|CONTENTS_SHOOTABLEBYARROW ); + } else if( spawnArgs.GetBool( "solid", "1" ) ) { + model->SetContents( CONTENTS_SOLID ); + } else { + model->SetContents( 0 ); + } + model->SetPosition( origin, axis ); + model->SetEntity( world ); + model->Link( clip ); + staticClipModels.Append( model ); + } +#if _HH_INLINED_PROC_CLIPMODELS + } +#endif + + if ( inlineEnt == 1 ) { // This means we must hand the model, too + renderEntity_t *renderEnt = new renderEntity_t; + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, renderEnt ); + HH_ASSERT( renderEnt->hModel && !renderEnt->callback && renderEnt->shaderParms[ SHADERPARM_ANY_DEFORM ] == DEFORMTYPE_NONE ); // Inlined statics can't have a callback + renderEnt->entityNum = 0; // WorldSpawn + gameRenderWorld->AddEntityDef( renderEnt ); + staticRenderEntities.Append( renderEnt ); + } + + // Don't spawn an entity for an inlined static + return true; + } else { + return idGameLocal::InhibitEntitySpawn( spawnArgs ); + } +} + +//HUMANHEAD rww +#if _HH_INLINED_PROC_CLIPMODELS +void hhGameLocal::CreateInlinedProcClip(idEntity *clipOwner) { + assert(world); + assert(inlinedProcClipModels.Num() == 0); //should never have existing models when this is called + int n = collisionModelManager->GetNumInlinedProcClipModels(); + if (n <= 0) { //nothing to work with, leave + return; + } + + for (int i = 0; i < n; i++) { + idClipModel *chunk = new idClipModel(va("%s%i", PROC_CLIPMODEL_STRING_PRFX, i)); + chunk->SetContents(CONTENTS_SOLID); + chunk->SetEntity(clipOwner); + chunk->Link(clip); + inlinedProcClipModels.Append(chunk); + } +} +#endif +//HUMANHEAD END + +// Finds entities matching a specified type. If last is NULL it starts at the beginning of the list. +// If last is valid, starts looking at that entities position in the list +idEntity *hhGameLocal::FindEntityOfType(const idTypeInfo &type, idEntity *last) { + idEntity *ent; + if (last) { + ent = last->activeNode.Next(); + } + else { + ent = activeEntities.Next(); + } + + for( ; ent != NULL; ent = ent->activeNode.Next() ) { + if (ent->IsType(type)) { + return ent; + } + } + return NULL; +} + +float hhGameLocal::GetDDAValue( void ) { + if ( !ddaManager ) { + return 0.5; + } else { + return ddaManager->GetDifficulty(); + } +} + + +void hhGameLocal::ClearStaticData( void ) { + delete dwWorldClipModel; //rww - this needs to be done as well, dw clipmodel expects world as owner. + dwWorldClipModel = NULL; + +#if _HH_INLINED_PROC_CLIPMODELS + inlinedProcClipModels.DeleteContents(true); //HUMANHEAD rww +#endif + + staticClipModels.DeleteContents( true ); // Clear any inlined static clip models + staticRenderEntities.DeleteContents( true ); // Clear any inlined static render entities +} + +float hhGameLocal::TimeBasedRandomFloat(void) { + if (gameLocal.isMultiplayer) { //rand based on time step + timeRandom = (1103515245 * timeRandom + 12345)%(1<<31); + return (timeRandom)/( float )( (1<<31) + 1 ); + } + else { //give sp complete randomness + return random.RandomFloat(); + } +} + +bool hhGameLocal::PlayerIsDeathwalking(void) { + HH_ASSERT( !isMultiplayer ); + hhPlayer *player = static_cast (GetLocalPlayer()); + HH_ASSERT( player ); + return player->IsDeathWalking(); +} + + +// Abstraction to hide the ugly code for text exchange between engine and game +void hhGameLocal::GetTip(const char *binding, idStr &keyMaterialString, idStr &keyString, bool &isWide) { + + char keyMaterial[256]; // No passing idStr between game and engine + char key[256]; // No passing idStr between game and engine + keyMaterial[0] = '\0'; + key[0] = '\0'; + if (binding) { + common->MaterialKeyForBinding(binding, keyMaterial, key, &isWide); + keyMaterialString = keyMaterial; + keyString = key; + } +} + +/* + hhGameLocal::SetTip + + gui gui to display tip + binding key binding to display associated key + tip textual tip to display + top optional top image + mtr optional material, overrides binding image + prefix optional prefix for gui state keys +*/ +bool hhGameLocal::SetTip(idUserInterface* gui, const char *binding, const char *tip, const char *topMaterial, const char *overrideMaterial, const char *prefix) { + assert(gui); + + idStr keyMaterial, key; + bool keywide = false; + if ( !spawnArgs.GetString("mtr_override", "", keyMaterial) ) { + if (binding) { + GetTip(binding, keyMaterial, key, keywide); + } + } + + const char *translated = common->GetLanguageDict()->GetString(tip); + + if (prefix != NULL) { + gui->SetStateBool( va("%s_keywide", prefix), keywide ); + gui->SetStateString( va("%s_tip", prefix), translated ? translated : "" ); + gui->SetStateString( va("%s_key", prefix), key.c_str() ); + gui->SetStateString( va("%s_keyMaterial", prefix), keyMaterial.c_str() ); + gui->SetStateString( va("%s_topMaterial", prefix), topMaterial ? topMaterial : "" ); + } + else { + gui->SetStateBool( "keywide", keywide ); + gui->SetStateString( "tip", translated ? translated : "" ); + gui->SetStateString( "key", key.c_str() ); + gui->SetStateString( "keyMaterial", keyMaterial.c_str() ); + gui->SetStateString( "topMaterial", topMaterial ? topMaterial : "" ); + } + + return (keyMaterial.Length() > 0); +} + diff --git a/neo/prey/Prey/prey_game.h b/neo/prey/Prey/prey_game.h new file mode 100644 index 00000000..1a1a1aeb --- /dev/null +++ b/neo/prey/Prey/prey_game.h @@ -0,0 +1,116 @@ +#ifndef __PREY_GAME_H +#define __PREY_GAME_H + +class hhReactionHandler; +class hhSunCorona; // CJR +class hhHand; +class hhAIInspector; + +#ifdef GAME_DLL +extern idCVar com_forceGenericSIMD; +#endif + +//HUMANHEAD: aob - needed for networking to send the least amount of bits +extern const int DECL_MAX_TYPES_NUM_BITS; +//HUMANHEAD END + +class hhGameLocal : public idGameLocal { +public: + virtual void Init( void ); + virtual void Shutdown( void ); + void UnregisterEntity( idEntity *ent ); + + virtual void MapShutdown( void ); + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ); + virtual void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f );// jrm + virtual void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); + //HUMANHEAD rww + virtual void LogitechLCDUpdate(void); + //HUMANHEAD END + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds ); + virtual void CacheDictionaryMedia( const idDict *dict ); + virtual bool Draw( int clientNum ); + + // added functionality functions + void RegisterTalonTarget( idEntity *ent ); + idEntity* SpawnClientObject( const char* objectName, idDict* additionalArgs ); //rww + idEntity* SpawnObject( const char* objectName, idDict* additionalArgs = NULL ); + + void GetTip(const char *binding, idStr &keyMaterialString, idStr &keyString, bool &isWide); + bool SetTip(idUserInterface* gui, const char *binding, const char *tip, const char *topMaterial = NULL, const char *overrideMaterial = NULL, const char *prefix = NULL); + void SetSunCorona( hhSunCorona *ent ) { sunCorona = ent; } // CJR + hhSunCorona * GetSunCorona( void ) { return sunCorona; } // CJR + idEntity * FindEntityOfType(const idTypeInfo &type, idEntity *last); + + // nla + idDict & GetSpawnArgs() { return( spawnArgs ); }; + hhReactionHandler* GetReactionHandler() { return reactionHandler; } + float GetTimeScale() const; + const idVec3& GetGravityNormal() { gravityNormal = gravity; gravityNormal.Normalize(); return gravityNormal; } + int SimpleMonstersWithinRadius( const idVec3 org, float radius, idAI **monstList, int maxCount = MAX_GENTITIES ) const; + + void SendMessageAI( const idEntity* entity, const idVec3& origin, float radius, const idEventDef& message ); + + const char* MatterTypeToMatterName( surfTypes_t type ) const; + const char* MatterTypeToMatterKey( const char* prefix, surfTypes_t type ) const; + surfTypes_t MatterNameToMatterType( const char* name ) const; + surfTypes_t GetMatterType( const trace_t& trace, const char* descriptor = "impact" ) const; + surfTypes_t GetMatterType( const idEntity *ent, const idMaterial *material, const char* descriptor = "impact" ) const; + + class hhDDAManager* GetDDA() { return ddaManager; } + + bool IsLOTA() { return bIsLOTA; } + void AlertAI( idEntity *ent ); + void AlertAI( idEntity *ent, float radius ); + + // Special case, these must be virtual -mdl + virtual void Save( idSaveGame *savefile ) const; + virtual void Restore( idRestoreGame *savefile ); + + float GetDDAValue( void ); + + void ClearStaticData( void ); + + float TimeBasedRandomFloat(void); //HUMANHEAD rww + +#if _HH_INLINED_PROC_CLIPMODELS + virtual void CreateInlinedProcClip(idEntity *clipOwner); //HUMANHEAD rww +#endif + + virtual bool PlayerIsDeathwalking( void ); + + idList talonTargets; + float lastAIAlertRadius; + + idList< idEntityPtr< hhHand > > hands; + + idEntityPtr inspector; + +protected: +#if DEATHWALK_AUTOLOAD + virtual void SpawnAppendedMapEntities(); +#endif + virtual bool InhibitEntitySpawn( idDict &spawnArgs ); // HUMANHEAD mdl + +protected: + virtual void SpawnMapEntities( void ); // JRM + idClipModel * dwWorldClipModel; +#if _HH_INLINED_PROC_CLIPMODELS + idList inlinedProcClipModels; //HUMANHEAD rww - chopped geometry grabbed from proc and turned into clipmodel data +#endif + + idVec3 gravityNormal; + + hhSunCorona *sunCorona; // CJR + + //HUMANHEAD rww - moved this down, it is using mixed memory inside and outside the managed heap as is, very messy. + //class hhDDAManager* ddaManager; + //HUMANHEAD END + + bool bIsLOTA; // CJR: If true, this map is a LOTA map + + idList staticClipModels; // HUMANHEAD mdl: For inlined static clip models + idList staticRenderEntities; // HUMANHEAD mdl: For inlined static models +}; + +#endif diff --git a/neo/prey/Prey/prey_items.cpp b/neo/prey/Prey/prey_items.cpp new file mode 100644 index 00000000..46064c1d --- /dev/null +++ b/neo/prey/Prey/prey_items.cpp @@ -0,0 +1,639 @@ +//************************************************************************** +//** +//** PREY_ITEMS.CPP +//** +//** Game code for Prey-specific items +//** +//************************************************************************** + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// hhItem +//========================================================================== +idEventDef EV_SetPickupState( "", "dd" ); + +CLASS_DECLARATION( idItem, hhItem ) + EVENT( EV_SetPickupState, hhItem::Event_SetPickupState ) + EVENT( EV_RespawnItem, hhItem::Event_Respawn ) +END_CLASS + +/* +================ +hhItem::Spawn +================ +*/ +void hhItem::Spawn() { + // Logic to allow item cabinets to deny pickups until desired + if( spawnArgs.GetBool("enablePickup", "1") ) { + EnablePickup(); + } else { + DisablePickup(); + } + + // Diversity for the blinking highlight shells + SetShaderParm(SHADERPARM_DIVERSITY, gameLocal.random.RandomFloat()); +} + +/* +================ +hhItem::EnablePickup +================ +*/ +void hhItem::EnablePickup() { + GetPhysics()->EnableClip(); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + canPickUp = !spawnArgs.GetBool("triggerFirst"); +} + +/* +================ +hhItem::DisablePickup +================ +*/ +void hhItem::DisablePickup() { + GetPhysics()->DisableClip(); + GetPhysics()->SetContents( 0 ); + canPickUp = false; +} + +/* +================ +hhItem::Pickup +================ +*/ +bool hhItem::Pickup( idPlayer *player ) { + if( gameLocal.isMultiplayer ) { + if( gameLocal.IsCooperative() ) { + CoopPickup( player ); + } else { + if (MultiplayerPickup( player )) { + //HUMANHEAD rww - see if the weapon has a dropped energy type on it + const char *droppedEnergy = spawnArgs.GetString("def_droppedEnergyType"); + if (droppedEnergy && droppedEnergy[0]) { //if it does, copy it to the player's inventory + const idDeclEntityDef *energyDecl = gameLocal.FindEntityDef(droppedEnergy, false); + if (energyDecl) { + const idDeclEntityDef *fireDecl = gameLocal.FindEntityDef(energyDecl->dict.GetString("def_fireInfo"), false); + if (fireDecl) { + hhPlayer *hhPl = static_cast(player); + int num = hhPl->GetWeaponNum("weaponobj_soulstripper"); + assert(num); + hhPl->inventory.energyType = droppedEnergy; + hhPl->weaponInfo[ num ].ammoMax = fireDecl->dict.GetInt("ammoAmount"); + hhPl->spawnArgs.SetInt( "max_ammo_energy", fireDecl->dict.GetInt("ammoAmount") ); + hhPl->inventory.ammo[hhPl->inventory.AmmoIndexForAmmoClass("ammo_energy")]=0; + hhPl->Give( "ammo_energy", fireDecl->dict.GetString("ammoAmount") ); + } + } + } + //HUMANHEAD END + } + } + } else { + SinglePlayerPickup( player ); + } + return true; +} + +/* +================ +hhItem::ShouldRespawn +================ +*/ +bool hhItem::ShouldRespawn( float* respawnDelay ) const { + float respawn = spawnArgs.GetFloat( "respawn", "5.0" ); + if (gameLocal.isMultiplayer) { //rww - override default in mp + respawn *= 2.0f; + } + + if( respawnDelay && respawn > 0.0f ) { + *respawnDelay = respawn; + } + + return (respawn > 0.0f) && !spawnArgs.GetBool( "dropped" ) && gameLocal.isMultiplayer; +} + +/* +================ +hhItem::PostRespawn +================ +*/ +void hhItem::PostRespawn( float delay ) { + const char *sfx = spawnArgs.GetString( "fxRespawn" ); + if ( sfx && *sfx ) { + PostEventSec( &EV_RespawnFx, delay - 0.5f ); + } + PostEventSec( &EV_RespawnItem, delay ); +} + +/* +================ +hhItem::DetermineRemoveOrRespawn +================ +*/ +void hhItem::DetermineRemoveOrRespawn( int removeDelay ) { + bool keepThinking = false; + + // clear our contents so the object isn't picked up twice + SetPickupState( 0, false ); + + if (gameLocal.isMultiplayer && !IsType(idMoveableItem::Type)) { //check for a respawning skin in mp + idStr respawningSkin; + if (spawnArgs.GetString("skin_itemRespawning", "", respawningSkin)) { + SetSkin(declManager->FindSkin(respawningSkin.c_str())); + keepThinking = true; + } + } + + if (!keepThinking) { + // hide the model + Hide(); + } + + // add the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + itemShellHandle = -1; + } + + float respawnDelay = 0.0f; + if ( ShouldRespawn(&respawnDelay) ) { + PostRespawn( respawnDelay ); + } else { + // give some time for the pickup sound to play + // FIXME: Play on the owner + PostEventMS( &EV_Remove, removeDelay ); + } + + if (!keepThinking) { + BecomeInactive( TH_THINK ); + } +} + +/* +================ +hhItem::AnnouncePickup +================ +*/ +int hhItem::AnnouncePickup( idPlayer* player ) { + ServerSendEvent( EVENT_PICKUP, NULL, false, -1 ); + + // play pickup sound + int soundLength = 0; + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, &soundLength ); + + // trigger our targets + ActivateTargets( player ); + + return soundLength; +} + +/* +================ +hhItem::SinglePlayerPickup +================ +*/ +void hhItem::SinglePlayerPickup( idPlayer *player ) { + if ( !GiveToPlayer( player ) ) { + return; + } + + DetermineRemoveOrRespawn( AnnouncePickup(player) ); +} + +/* +================ +hhItem::CoopPickup +================ +*/ +void hhItem::CoopPickup( idPlayer* player ) { + const char* weaponDef = spawnArgs.GetString( "def_weapon" ); + if( weaponDef && weaponDef[0] ) { + CoopWeaponPickup( player ); + } else { + CoopItemPickup( player ); + } +} + +/* +================ +hhItem::CoopWeaponPickup +================ +*/ +void hhItem::CoopWeaponPickup( idPlayer *player ) { + float delay = 0.0f; + + if( !GiveToPlayer(player) ) { + return; + } + + AnnouncePickup( player ); + + ShouldRespawn( &delay ); + SetPickupState( 0, false ); + PostEventSec( &EV_SetPickupState, delay, CONTENTS_TRIGGER, true ); +} + +/* +================ +hhItem::CoopItemPickup +================ +*/ +void hhItem::CoopItemPickup( idPlayer *player ) { + idEntity* entity = NULL; + hhPlayer* castPlayer = NULL; + + for( int ix = 0; ix < gameLocal.numClients; ++ix ) { + entity = gameLocal.entities[ix]; + if( !entity || !entity->IsType(hhPlayer::Type) ) { + continue; + } + + castPlayer = static_cast( entity ); + castPlayer->GiveItem( this ); + } + + DetermineRemoveOrRespawn( AnnouncePickup(player) ); +} + +/* +================ +hhItem::MultiplayerPickup +================ +*/ +bool hhItem::MultiplayerPickup( idPlayer *player ) { + if( !GiveToPlayer(player) ) { + return false; + } + + DetermineRemoveOrRespawn( AnnouncePickup(player) ); + return true; +} + +/* +================ +hhItem::SetPickupState +================ +*/ +void hhItem::SetPickupState( int contents, bool allowPickup ) { + GetPhysics()->SetContents( contents ); + canPickUp = allowPickup; +} + +/* +================ +hhItem::Event_SetPickupState +================ +*/ +void hhItem::Event_SetPickupState( int contents, bool allowPickup ) { + SetPickupState( contents, allowPickup ); +} + +/* +================ +hhItem::Event_Respawn +================ +*/ +void hhItem::Event_Respawn( void ) { + if ( !gameLocal.isClient ) { + ServerSendEvent( EVENT_RESPAWN, NULL, false, -1 ); + } + BecomeActive( TH_THINK ); + if (gameLocal.isMultiplayer && renderEntity.customSkin != GetNonRespawnSkin()) { + SetSkin(GetNonRespawnSkin()); //restore skin + } + Show(); + inViewTime = -1000; + lastCycle = -1; + + //HUMANHEAD: aob + SetPickupState( CONTENTS_TRIGGER, true ); + //HUMANHEAD END + + SetOrigin( orgOrigin ); + StartSound( "snd_respawn", SND_CHANNEL_ITEM ); +} + + +//============================================================================= +// hhItemSoul +//============================================================================= + +idEventDef EV_PlaySpiritSound( "playSpiritSound", NULL ); +idEventDef EV_Broadcast_AssignFx_Spirit( "", "e" ); +idEventDef EV_Broadcast_AssignFx_Physical( "", "e" ); + +CLASS_DECLARATION( hhItem, hhItemSoul ) + EVENT( EV_TalonAction, hhItemSoul::Event_TalonAction ) + EVENT( EV_PostSpawn, hhItemSoul::Event_PostSpawn ) + EVENT( EV_PlaySpiritSound, hhItemSoul::Event_PlaySpiritSound ) + EVENT( EV_Broadcast_AssignFx_Spirit, hhItemSoul::Event_AssignFxSoul_Spirit ) + EVENT( EV_Broadcast_AssignFx_Physical, hhItemSoul::Event_AssignFxSoul_Physical ) +END_CLASS + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Spawn +// +//-------------------------------------------------------------------------- +void hhItemSoul::Spawn() { + idDict args; + idVec3 offset; + + fl.networkSync = true; //rww + fl.clientEvents = true; //rww + + spawnArgs.SetBool("dropped", true); //rww - don't respawn + + bFollowTriggered = false; + + // Only allow this item to be spawned if we can spirit walk + if (!gameLocal.isMultiplayer) { + hhPlayer *player = static_cast( gameLocal.GetLocalPlayer() ); + if ( player ) { + if ( !player->inventory.requirements.bCanSpiritWalk ) { + Hide(); + PostEventMS( &EV_Remove, 0 ); // Remove this soul before it can be spawned + return; + } + } + } + + BecomeActive( TH_THINK | TH_PHYSICS ); + + // Remove the lifeforce after 60 seconds if it hasn't been picked up + if (gameLocal.isMultiplayer) { //rww + PostEventSec( &EV_Remove, spawnArgs.GetFloat( "lifeTime_mp", "30" ) ); + } + else { + PostEventSec( &EV_Remove, spawnArgs.GetFloat( "lifeTime", "60" ) ); + } + + // Play the spirit sound, after a 10 second delay + PostEventSec( &EV_PlaySpiritSound, spawnArgs.GetFloat( "spiritSoundDelay", "10" ) ); + + renderEntity.onlyVisibleInSpirit = true; + + velocity = vec3_origin; + acceleration = vec3_origin; + surge = 0.0f; + parentMonster = NULL; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_PostSpawn +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_PostSpawn() { + hhFxInfo fxInfo; + + if (!gameLocal.isMultiplayer) { + const char *monsterName = spawnArgs.GetString("monsterSpawnedBy", NULL); + if (monsterName) { + idEntity *ent = gameLocal.FindEntity(monsterName); + if (ent && ent->IsType(idAI::Type)) { + parentMonster = ent; + } + } + } + + // Spawn the spirit-only fx (this fx is only seen when spiritwalking) + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( false ); + fxInfo.OnlyVisibleInSpirit( true ); + BroadcastFxInfo( spawnArgs.GetString("fx_lifeforce"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx_Spirit, false ); //rww - local + + // Spawn the physical realm fx + fxInfo.SetNormal( GetAxis()[2] ); + fxInfo.RemoveWhenDone( false ); + fxInfo.OnlyVisibleInSpirit( false ); + BroadcastFxInfo( spawnArgs.GetString("fx_lifeforcePhysical"), GetOrigin(), GetAxis(), &fxInfo, &EV_Broadcast_AssignFx_Physical, false ); //rww - local +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_AssignFxSoul +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_AssignFxSoul_Spirit( hhEntityFx* fx ) { + soulFx = fx; +} + +void hhItemSoul::Event_AssignFxSoul_Physical( hhEntityFx* fx ) { + physicalSoulFx = fx; +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_PlaySpiritSound +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_PlaySpiritSound() { + StartSound( "snd_spiritItemNear", SND_CHANNEL_SPIRITWALK, 0, true ); + + // Randomly start the sound over again (random delay between 12 and 22 seconds) + PostEventSec( &EV_PlaySpiritSound, 12.0f + spawnArgs.GetFloat( "spiritSoundDelay", "10.0" ) * gameLocal.random.RandomFloat() ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::~hhItemSoul +// +//-------------------------------------------------------------------------- + +hhItemSoul::~hhItemSoul() { + StopSound( SND_CHANNEL_SPIRITWALK, false ); //stop now, do not broadcast on purpose + SAFE_REMOVE( soulFx ); + SAFE_REMOVE( physicalSoulFx ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Ticker +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Think() { + idVec3 playerOrigin; + idMat3 playerAxis; + // Move the soul towards the nearest spirit player + //COOP FIXME: Get the NEAREST spirit player for this to support multiplayer and coop. + + hhPlayer *player = NULL; + if (!gameLocal.isMultiplayer) { + player = static_cast( gameLocal.GetLocalPlayer() ); + } + else { + orgOrigin = GetOrigin(); + spin = false; + } + float followSpeed = spawnArgs.GetFloat( "followSpeed", "20" ); + + if ( !player || player->noclip ) { + velocity = vec3_origin; // mdl: If player is noclip, don't move an inch + } else { + player->GetViewPos( playerOrigin, playerAxis ); + + if ( !bFollowTriggered && player && player->IsSpiritWalking() ) { + acceleration = ( playerOrigin - playerAxis[2] * 20 ) - GetOrigin(); + float dot = playerAxis[0] * -acceleration; + + if ( dot > 0.95f ) { // Accelerate only if the player is looking at the soul + bFollowTriggered = true; + lastPlayerOrigin = playerOrigin; + } + } + + if ( bFollowTriggered ) { + //HUMANHEAD PCF mdl 04/28/06 - Changed factor from 0.01 to 0.05 for stationary players + float factor = 0.05f; // Keep acceleration slow enough for the player to see + if ( ( lastPlayerOrigin - playerOrigin ).LengthSqr() > 75.0f ) { + velocity = vec3_origin; // Player has moved, so clear the old velocity + lastPlayerOrigin = playerOrigin; + //HUMANHEAD PCF mdl 04/28/06 - Changed factor from 5 to 3.5 for moving players + factor = 3.5f; // Give acceleration a little boost so the soul doesn't seem slow when the player is moving around. + } + + acceleration = ( playerOrigin - playerAxis[2] * 20 ) - GetOrigin(); + velocity += (factor * acceleration * ( spawnArgs.GetFloat( "acceleration", "0.5" ) * spawnArgs.GetFloat( "surge", "0.01" ) ) * (60.0f * USERCMD_ONE_OVER_HZ)); + + SetOrigin( GetOrigin() + velocity ); + UpdateVisuals(); + + if ( soulFx.IsValid() ) { + soulFx->SetOrigin( GetOrigin() ); + soulFx->UpdateVisuals(); + } + if ( physicalSoulFx.IsValid() ) { + physicalSoulFx->SetOrigin( GetOrigin() ); + physicalSoulFx->UpdateVisuals(); + } + } + } + + // Must be done after this movement for proper relinking + hhItem::Think(); +} + +//rww - netcode +void hhItemSoul::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot(msg); + msg.WriteBits(IsHidden() || !soulFx.IsValid() || !soulFx->IsActive(TH_THINK), 1); +} + +void hhItemSoul::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot(msg); + bool hidden = !!msg.ReadBits(1); + if (hidden != IsHidden()) { + if (hidden) { + if( soulFx.IsValid() ) { + soulFx->Nozzle( false ); + } + if( physicalSoulFx.IsValid() ) { + physicalSoulFx->Nozzle( false ); + } + Hide(); + } + else { + Show(); + } + } +} + +void hhItemSoul::ClientPredictionThink( void ) { + Think(); +} + + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Pickup +// +//-------------------------------------------------------------------------- + +bool hhItemSoul::Pickup( idPlayer *player ) { + + CancelEvents( &EV_Remove ); // Remove the 60-second cancel event from when this item was spawned + CancelEvents(&EV_PlaySpiritSound); //rww - also cancel pending spirit sound events + + StopSound( SND_CHANNEL_SPIRITWALK, true ); + + bool result = hhItem::Pickup( player ); + if( soulFx.IsValid() ) { + soulFx->Nozzle( false ); + } + if( physicalSoulFx.IsValid() ) { + physicalSoulFx->Nozzle( false ); + } + + return result; +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_TalonAction +// +//-------------------------------------------------------------------------- + +void hhItemSoul::Event_TalonAction( idEntity *talon, bool landed ) { + idPlayer *player; + + if( !talon ) { + return; + } + + player = (idPlayer *)(((hhTalon *)talon)->GetOwner()); + Pickup( player ); +} + +//-------------------------------------------------------------------------- +// +// hhItemSoul::Event_Remove +// +//-------------------------------------------------------------------------- +void hhItemSoul::Event_Remove() { + // Now that spirit is gone, start the disposal countdown + if (parentMonster.IsValid()) { + parentMonster->StartDisposeCountdown(); + } + + hhItem::Event_Remove(); +} + +//================ +//hhItemSoul::Save +//================ +void hhItemSoul::Save( idSaveGame *savefile ) const { + soulFx.Save( savefile ); + physicalSoulFx.Save( savefile ); + parentMonster.Save( savefile ); + savefile->WriteVec3( velocity ); + savefile->WriteVec3( acceleration ); + savefile->WriteFloat( surge ); + savefile->WriteBool( bFollowTriggered ); + savefile->WriteVec3( lastPlayerOrigin ); +} + +//================ +//hhItemSoul::Restore +//================ +void hhItemSoul::Restore( idRestoreGame *savefile ) { + soulFx.Restore( savefile ); + physicalSoulFx.Restore( savefile ); + parentMonster.Restore( savefile ); + savefile->ReadVec3( velocity ); + savefile->ReadVec3( acceleration ); + savefile->ReadFloat( surge ); + savefile->ReadBool( bFollowTriggered ); + savefile->ReadVec3( lastPlayerOrigin ); +} + diff --git a/neo/prey/Prey/prey_items.h b/neo/prey/Prey/prey_items.h new file mode 100644 index 00000000..e297ae0d --- /dev/null +++ b/neo/prey/Prey/prey_items.h @@ -0,0 +1,79 @@ +#ifndef __PREY_ITEMS_H__ +#define __PREY_ITEMS_H__ + +// These defined in item.cpp +extern const idEventDef EV_RespawnItem; +extern const idEventDef EV_HideObjective; +extern const idEventDef EV_HideItem; + +class hhItem: public idItem { + CLASS_PROTOTYPE( hhItem ); + + public: + void Spawn(); + + virtual bool Pickup( idPlayer *player ); + + void EnablePickup(); + void DisablePickup(); + + protected: + bool ShouldRespawn( float* respawnDelay = NULL ) const; + void PostRespawn( float delay ); + void DetermineRemoveOrRespawn( int removeDelay ); + int AnnouncePickup( idPlayer* player ); + + void SinglePlayerPickup( idPlayer *player ); + + void CoopPickup( idPlayer* player ); + void CoopWeaponPickup( idPlayer *player ); + void CoopItemPickup( idPlayer *player ); + + bool MultiplayerPickup( idPlayer *player ); + + void SetPickupState( int contents, bool allowPickup ); + + protected: + void Event_SetPickupState( int contents, bool allowPickup ); + void Event_Respawn( void ); +}; + +class hhItemSoul : public hhItem { + CLASS_PROTOTYPE(hhItemSoul); + + public: + void Spawn(); + virtual ~hhItemSoul(); + + virtual void Think(); + + //rww - netcode + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + + virtual bool Pickup( idPlayer *player ); + void Event_TalonAction( idEntity *talon, bool landed ); + void Event_PostSpawn(); + void Event_PlaySpiritSound(); + void Event_AssignFxSoul_Spirit( hhEntityFx* fx ); + void Event_AssignFxSoul_Physical( hhEntityFx* fx ); + + virtual void Event_Remove(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + idEntityPtr soulFx; + idEntityPtr physicalSoulFx; + idEntityPtr parentMonster; + idVec3 velocity; + idVec3 acceleration; + float surge; + bool bFollowTriggered; + idVec3 lastPlayerOrigin; +}; + + +#endif /* __PREY_ITEMS_H__ */ diff --git a/neo/prey/Prey/prey_liquid.cpp b/neo/prey/Prey/prey_liquid.cpp new file mode 100644 index 00000000..7c28a987 --- /dev/null +++ b/neo/prey/Prey/prey_liquid.cpp @@ -0,0 +1,105 @@ +//************************************************************************** +//** +//** PREY_LIQUID.CPP +//** +//** Game code for Prey-specific dynamic liquid +//** +//** Currently using id's liquid code for the rendering +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +//========================================================================== +// +// hhLiquid +// +//========================================================================== +const idEventDef EV_Disturb("disturb", "vff"); + +CLASS_DECLARATION( hhRenderEntity, hhLiquid ) + EVENT( EV_Touch, hhLiquid::Event_Touch ) + EVENT( EV_Disturb, hhLiquid::Event_Disturb ) +END_CLASS + +void hhLiquid::Spawn(void) { + if (renderEntity.hModel) { + renderEntity.hModel->Reset(); + } + + fl.takedamage = true; + GetPhysics()->SetContents( CONTENTS_WATER | CONTENTS_TRIGGER | CONTENTS_RENDERMODEL ); + + factor_movement = spawnArgs.GetFloat("factor_movement"); + factor_collide = spawnArgs.GetFloat("factor_collide"); +} + +void hhLiquid::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( factor_movement ); + savefile->WriteFloat( factor_collide ); +} + +void hhLiquid::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( factor_movement ); + savefile->ReadFloat( factor_collide ); + + if (renderEntity.hModel) { + renderEntity.hModel->Reset(); + } +} + +void hhLiquid::Disturb( const idVec3 &point, const idBounds &bounds, const float magnitude ) { + if (renderEntity.hModel) { + idVec3 relativeToModel = point - GetPhysics()->GetOrigin(); + //FIXME: Take rotation of 'this' into account + +#if 0 + hhUtils::DebugCross(colorRed, point, 5, 1000); + gameLocal.Printf("Disturbance: Bounds={%.0f,%.0f,%.0f)(%.0f,%.0f,%.0f) mag=%f\n", + bounds[0].x, bounds[0].y, bounds[0].z, + bounds[1].x, bounds[1].y, bounds[1].z, + magnitude); +#endif + + // Pass in bounds in model coords + renderEntity.hModel->IntersectBounds( bounds.Translate(relativeToModel), magnitude ); + } +} + +void hhLiquid::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + idBounds bounds(idVec3(-1,-1,-1), idVec3(1,1,1)); + if (inflictor) { + bounds = inflictor->GetPhysics()->GetBounds(); + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if (damageDef) { + float disturbance = damageDef->GetFloat("liquid_disturbance"); + float boundsfactor = damageDef->GetFloat("liquid_bounds_expand"); + Disturb(inflictor->GetPhysics()->GetOrigin(), bounds.Expand(boundsfactor), disturbance); + } +} + +void hhLiquid::Event_Touch(idEntity *other, trace_t *trace) { + + idVec3 vel = other->GetPhysics()->GetLinearVelocity(); + idVec3 pos = other->GetPhysics()->GetOrigin(); + bool bMoving = vel.LengthSqr() > 1.0f; + if (bMoving) { + //TODO: Make scale with velocity + + // Since deforming completely to the bounds is a little too much, average the + // z position of the actor with the surface + pos.z = (pos.z + GetOrigin().z) * 0.5f; + Disturb(pos, other->GetPhysics()->GetBounds(), factor_movement); + } +} + +void hhLiquid::Event_Disturb(const idVec3 &position, float size, float magnitude) { + idBounds bounds(idVec3(-0.5f,-0.5f,-0.5f), idVec3(0.5f,0.5f,0.5f)); + Disturb(position, bounds.Expand(size), magnitude); +} diff --git a/neo/prey/Prey/prey_liquid.h b/neo/prey/Prey/prey_liquid.h new file mode 100644 index 00000000..9fc4605d --- /dev/null +++ b/neo/prey/Prey/prey_liquid.h @@ -0,0 +1,26 @@ + +#ifndef __PREY_SLUDGE_H__ +#define __PREY_SLUDGE_H__ + +class hhLiquid : public hhRenderEntity { +public: + CLASS_PROTOTYPE( hhLiquid ); + + void Spawn(void); + 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 ); + + void Disturb( const idVec3 &point, const idBounds &bounds, const float magnitude ); + +protected: + virtual void Event_Touch(idEntity *other, trace_t *trace); + void Event_Disturb(const idVec3 &position, float size, float magnitude); + +protected: + float factor_movement; + float factor_collide; +}; + + +#endif /* __PREY_SLUDGE_H__ */ diff --git a/neo/prey/Prey/prey_local.cpp b/neo/prey/Prey/prey_local.cpp new file mode 100644 index 00000000..a000f528 --- /dev/null +++ b/neo/prey/Prey/prey_local.cpp @@ -0,0 +1,3 @@ +#include "precompiled.h" +#pragma hdrstop +#include "prey_local.h" \ No newline at end of file diff --git a/neo/prey/Prey/prey_local.h b/neo/prey/Prey/prey_local.h new file mode 100644 index 00000000..ad924ef0 --- /dev/null +++ b/neo/prey/Prey/prey_local.h @@ -0,0 +1,115 @@ +#ifndef __PREY_LOCAL_H +#define __PREY_LOCAL_H + +#include "../Game/game_local.h" +#include "prey_game.h" +#include "sys_preycmds.h" + +#include "force_converge.h" +#include "game_targetproxy.h" +#include "sys_debugger.h" +#include "game_console.h" +#include "game_spring.h" +#include "game_misc.h" +#include "game_moveable.h" +#include "game_barrel.h" +#include "physics_vehicle.h" +#include "game_vehicle.h" +#include "game_shuttle.h" +#include "game_railshuttle.h" +#include "game_dockedgun.h" +#include "game_player.h" +#include "prey_beam.h" +#include "game_trigger.h" +#include "game_tripwire.h" +#include "prey_bonecontroller.h" +#include "game_securityeye.h" +#include "game_cards.h" +#include "game_arcadegame.h" +#include "game_jukebox.h" +#include "game_blackjack.h" +#include "game_poker.h" +#include "game_zone.h" +#include "game_safeDeathVolume.h" +#include "game_energynode.h" +#include "prey_weaponrifle.h" +#include "prey_weaponspiritbow.h" +#include "prey_weaponautocannon.h" +#include "prey_weaponcrawlergrenade.h" +#include "prey_weaponrocketlauncher.h" +#include "prey_weaponsoulstripper.h" +#include "prey_weaponhider.h" +#include "game_jumpzone.h" +#include "game_note.h" +#include "game_portal.h" +#include "game_skybox.h" +#include "game_trackmover.h" +#include "game_sphere.h" +#include "prey_items.h" +#include "game_modeltoggle.h" +#include "game_animator.h" +#include "game_targets.h" +#include "game_slots.h" +#include "game_vomiter.h" +#include "game_modeldoor.h" +#include "game_door.h" +#include "game_cilia.h" +#include "game_animatedgui.h" +#include "game_gibbable.h" +//#include "game_pathNode.h" +#include "game_organtrigger.h" +#include "game_gravityswitch.h" + +#include "ai_passageway.h" +#include "ai_speech.h" +#include "ai_reaction.h" +#include "game_monster_ai.h" +#include "game_entityspawner.h" +#include "ai_centurion.h" +#include "ai_droid.h" +#include "ai_mutilatedhuman.h" +#include "ai_mutate.h" +#include "ai_hunter_simple.h" +#include "ai_creaturex.h" +#include "ai_harvester_simple.h" +#include "ai_jetpack_harvester_simple.h" +#include "ai_keeper_simple.h" +#include "ai_gasbag_simple.h" +#include "ai_spawncase.h" +#include "ai_inspector.h" +#include "ai_possessedTommy.h" +#include "ai_sphereboss.h" +#include "ai_crawler.h" + +#include "game_itemcabinet.h" +#include "game_itemautomatic.h" +#include "game_healthspore.h" +#include "game_healthbasin.h" +#include "game_portalframe.h" +#include "game_afs.h" +#include "game_lightfixture.h" +#include "game_mover.h" +#include "game_gun.h" +#include "game_wraith.h" +#include "game_deathwraith.h" +#include "game_talon.h" +#include "game_debrisspawner.h" +#include "game_bindController.h" +#include "game_rail.h" +#include "game_mountedgun.h" +#include "game_fixedpod.h" +#include "game_mine.h" +#include "game_pod.h" +#include "game_podspawner.h" +#include "game_shuttledock.h" +#include "game_shuttletransport.h" +#include "prey_spiritbridge.h" +#include "prey_spiritsecret.h" +#include "game_forcefield.h" +#include "prey_liquid.h" +#include "game_alarm.h" +#include "game_sunCorona.h" +#include "game_eggspawner.h" +#include "game_proxdoor.h" + +#endif diff --git a/neo/prey/Prey/prey_projectile.cpp b/neo/prey/Prey/prey_projectile.cpp new file mode 100644 index 00000000..278dfb0b --- /dev/null +++ b/neo/prey/Prey/prey_projectile.cpp @@ -0,0 +1,1547 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_SpawnDriverLocal( "", "s" ); +const idEventDef EV_SpawnFxFlyLocal( "", "s" ); + +const idEventDef EV_Collision_Flesh( "", "tv", 'd' ); +const idEventDef EV_Collision_Metal( "", "tv", 'd' ); +const idEventDef EV_Collision_AltMetal( "", "tv", 'd' ); +const idEventDef EV_Collision_Wood( "", "tv", 'd' ); +const idEventDef EV_Collision_Stone( "", "tv", 'd' ); +const idEventDef EV_Collision_Glass( "", "tv", 'd' ); +const idEventDef EV_Collision_Liquid( "", "tv", 'd' ); +const idEventDef EV_Collision_Spirit( "", "tv", 'd' ); +const idEventDef EV_Collision_Remove( "", "tv", 'd' ); +const idEventDef EV_Collision_CardBoard( "", "tv", 'd' ); +const idEventDef EV_Collision_Tile( "", "tv", 'd' ); +const idEventDef EV_Collision_Forcefield( "", "tv", 'd' ); +const idEventDef EV_Collision_Chaff( "", "tv", 'd' ); +const idEventDef EV_Collision_Wallwalk( "", "tv", 'd' ); +const idEventDef EV_Collision_Pipe( "", "tv", 'd' ); + + +const idEventDef EV_AllowCollision_Flesh( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Metal( "", "t", 'd' ); +const idEventDef EV_AllowCollision_AltMetal( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Wood( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Stone( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Glass( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Liquid( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Spirit( "", "t", 'd' ); +const idEventDef EV_AllowCollision_CardBoard( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Tile( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Forcefield( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Chaff( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Wallwalk( "", "t", 'd' ); +const idEventDef EV_AllowCollision_Pipe( "", "t", 'd' ); + +hhMatterEventDefPartner matterEventsCollision( "collision" ); +hhMatterEventDefPartner matterEventsAllowCollision( "collision_allow" ); + +CLASS_DECLARATION( idProjectile, hhProjectile ) + EVENT( EV_Explode, hhProjectile::Event_Fuse_Explode ) + EVENT( EV_SpawnDriverLocal, hhProjectile::Event_SpawnDriverLocal ) + EVENT( EV_SpawnFxFlyLocal, hhProjectile::Event_SpawnFxFlyLocal ) + + EVENT( EV_Collision_Flesh, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Metal, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_AltMetal, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Wood, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Stone, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Glass, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Liquid, hhProjectile::Event_Collision_DisturbLiquid ) + EVENT( EV_Collision_CardBoard, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Tile, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Forcefield, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Pipe, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Wallwalk, hhProjectile::Event_Collision_Impact ) + EVENT( EV_Collision_Chaff, hhProjectile::Event_Collision_Impact ) + + EVENT( EV_Collision_Remove, hhProjectile::Event_Collision_Remove ) + + EVENT( EV_AllowCollision_Flesh, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Metal, hhProjectile::Event_AllowCollision_CollideNoProj ) + EVENT( EV_AllowCollision_AltMetal, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Wood, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Stone, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Glass, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Liquid, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_CardBoard, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Tile, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Forcefield, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Pipe, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Wallwalk, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Spirit, hhProjectile::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Chaff, hhProjectile::Event_AllowCollision_Collide ) //bjk: shield blocks all +END_CLASS + + +/* +================ +hhProjectile::Spawn +================ +*/ +void hhProjectile::Spawn() { + bDDACounted = false; + parentProjectile = NULL; + launchTimestamp = 0; + + collidedPortal = NULL; // cjr + collideLocation = vec3_origin; // cjr + collideVelocity = vec3_origin; // cjr + + weaponNum = -1; // cjr - default the weapon index to -1, to denote non-player weapons + + netSyncPhysics = spawnArgs.GetBool( "net_fullphysics" ); //HUMANHEAD rww + bNoCollideWithCrawlers = spawnArgs.GetBool( "noCollideWithCrawlers", "0" ); + bProjCollide = spawnArgs.GetBool( "proj_collision", "0" ); + flyBySoundDistSq = spawnArgs.GetFloat( "flyby_dist", "0" ); + flyBySoundDistSq *= flyBySoundDistSq; + if ( flyBySoundDistSq > 0 ) { + bPlayFlyBySound = true; + } + BecomeActive( TH_TICKER ); +} + +/* +================ +hhProjectile::~hhProjectile +================ +*/ +hhProjectile::~hhProjectile() { + SAFE_REMOVE(fxFly); //HUMANHEAD rww +} + +void hhProjectile::SetParentProjectile( hhProjectile* in_parent ) { + parentProjectile.Assign( in_parent ); +} + +void hhProjectile::SetCollidedPortal( hhPortal *newPortal, idVec3 newLocation, idVec3 newVelocity ) { + collidedPortal.Assign( newPortal ); + collideLocation = newLocation; + collideVelocity = newVelocity; + BecomeActive(TH_MISC1); + + physicsObj.PutToRest(); +} + +hhProjectile* hhProjectile::GetParentProjectile( void ) { + if( parentProjectile.IsValid() ) { + return parentProjectile.GetEntity(); + } + return NULL; +} + + +/* +================ +hhProjectile::SetOrigin +================ +*/ +void hhProjectile::SetOrigin( const idVec3& origin ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idVec3 localOrigin( origin ); + + if( driver.IsValid() && IsBoundTo(driver.GetEntity()) ) { + GetMasterPosition( masterOrigin, masterAxis ); + localOrigin = (localOrigin - masterOrigin) * masterAxis.Transpose(); + } + + idProjectile::SetOrigin( localOrigin ); +} + +/* +================ +hhProjectile::SetAxis +================ +*/ +void hhProjectile::SetAxis( const idMat3& axis ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idMat3 localAxis( axis ); + + if( driver.IsValid() && IsBoundTo(driver.GetEntity()) ) { + GetMasterPosition( masterOrigin, masterAxis ); + localAxis *= masterAxis.Transpose(); + } + + idProjectile::SetAxis( localAxis ); +} + +/* +================ +hhProjectile::Think +================ +*/ +void hhProjectile::Think( void ) { + + // HUMANHEAD: cjr - if this projectile recently struck a portal, then attempt to portal it + if ( (thinkFlags & TH_MISC1) && collidedPortal.IsValid() ) { + GetPhysics()->SetLinearVelocity( collideVelocity ); + collidedPortal->PortalProjectile( this, collideLocation, collideLocation + collideVelocity ); + collidedPortal = NULL; + collideLocation = vec3_origin; + collideVelocity = vec3_origin; + BecomeInactive(TH_MISC1); + } + // HUMANHEAD END + + // run physics + RunPhysics(); + + if( thinkFlags & TH_THINK ) { + //HUMANHEAD: aob - added thrust_start check + if( thrust && (thrust_start <= gameLocal.GetTime() && gameLocal.GetTime() < thrust_end) ) { + // evaluate force + //HUMANHEAD rww - get rid of the thruster, needless projectile constructor overhead. + //thruster.SetForce( GetAxis()[ 0 ] * thrust ); + //thruster.Evaluate( gameLocal.GetTime() ); + //replaced the logic for the thing here. + idVec3 force = GetAxis()[ 0 ] * thrust; + idVec3 point = physicsObj.GetCenterOfMass(); + idVec3 p = GetPhysics()->GetOrigin() + point * physics->GetAxis(); + GetPhysics()->AddForce( 0, p, force ); + } + } + + //HUMANHEAD: aob + if( thinkFlags & TH_TICKER ) { + Ticker(); + } + //HUMANHEAD END + + Present(); + + if ( thinkFlags & TH_MISC2 ) { + UpdateLight(); + } + + //HUMANHEAD jsh flyby sounds + if( !gameLocal.isMultiplayer && bPlayFlyBySound && gameLocal.GetLocalPlayer() ) { + if ( !owner.IsEqualTo( gameLocal.GetLocalPlayer() ) ) { + if ( (GetOrigin() - gameLocal.GetLocalPlayer()->GetOrigin()).LengthSqr() < flyBySoundDistSq ) { + BroadcastFxPrefixedRandom( "fx_flyby", EV_SpawnFxFlyLocal ); + StartSound( "snd_flyby", SND_CHANNEL_BODY ); + bPlayFlyBySound = false; + } + } + } +} + +/* +===================== +hhProjectile::PlayImpactSound + +custom for projectiles. this needs to broadcast, unless we get predicted projectiles operational. +however predicted projectiles are not reliable since the client will not always collide them before +the server does, and so they will get removed with no fx. not a serious issue but somewhat bothersome +nonetheless. +===================== +*/ +int hhProjectile::PlayImpactSound( const idDict* dict, const idVec3 &origin, surfTypes_t type ) { + const char *snd = gameLocal.MatterTypeToMatterKey( "snd_impact", type ); + if( !snd || !snd[0] || !dict ) { + return -1; + } + + int length = 0; + StartSoundShader( declManager->FindSound(dict->GetString(snd), false), SND_CHANNEL_BODY, 0, true, &length ); + return length; +} + +/* +============== +hhProjectile::UpdateLight +============== +*/ +void hhProjectile::UpdateLight() { + // Attempt to remove light + if( lightStartTime != lightEndTime && gameLocal.GetTime() >= lightEndTime ) { + FreeLightDef(); + BecomeInactive(TH_MISC2); + } + + if( lightDefHandle != -1 ) { + UpdateLightPosition(); + UpdateLightFade(); + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } +} + +/* +============== +hhProjectile::UpdateLightPosition +============== +*/ +void hhProjectile::UpdateLightPosition() { + renderLight.origin = GetOrigin() + GetAxis() * lightOffset; + renderLight.axis = GetAxis(); +} + +/* +============== +hhProjectile::UpdateLightFade +============== +*/ +void hhProjectile::UpdateLightFade() { + idVec3 color( vec3_zero ); + float frac = 0.0f; + + int time = gameLocal.GetTime(); + if( lightStartTime != lightEndTime && time < lightEndTime ) { + frac = MS2SEC(gameLocal.GetTime() - lightStartTime) / MS2SEC(lightEndTime - lightStartTime); + color.Lerp( lightColor, color, frac ); + + for( int ix = 0; ix < 3; ++ix ) { + renderLight.shaderParms[SHADERPARM_RED + ix] = color[ix]; + } + } +} + +/* +============== +hhProjectile::CreateLight +============== +*/ +int hhProjectile::CreateLight( const char* shaderName, const idVec3& size, const idVec3& color, const idVec3& offset, float fadeTime ) { + int fadeDuration = 0; + + if ( size.x <= 0.0f || !g_projectileLights.GetBool() ) { + return 0; + } + + if( size.Compare(vec3_zero, VECTOR_EPSILON) ) { + return 0; + } + + SIMDProcessor->Memset( &renderLight, 0, sizeof(renderLight) ); + FreeLightDef(); + + if( !shaderName || !shaderName[0] ) { + return 0; + } + + UpdateLightPosition(); + + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius = size; + renderLight.shaderParms[SHADERPARM_RED] = color[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = color[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = color[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.GetTime() ); + renderLight.noShadows = true; //HUMANHEAD bjk: cheaper + + fadeDuration = SEC2MS( fadeTime ); + + lightOffset = offset; + lightColor = color; + lightStartTime = gameLocal.GetTime(); + lightEndTime = lightStartTime + fadeDuration; + + BecomeActive(TH_MISC2); + + if( lightDefHandle != -1 ) { + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + return fadeDuration; +} + +/* +============== +hhProjectile::BounceSplat +============== +*/ +void hhProjectile::BounceSplat( const idVec3& origin, const idVec3& dir ) { + float size = hhMath::Lerp( spawnArgs.GetVec2("decal_bounce_size", "1.8 2.2"), gameLocal.random.RandomFloat() ); + const char* decal = spawnArgs.RandomPrefix( "mtr_bounce_shader", gameLocal.random ); + if( decal && *decal ) { + gameLocal.ProjectDecal( origin, dir, 16.0f, true, size, decal ); + } +} + + +/* +================ +hhProjectile::Create + +HUMANHEAD: AOBMERGE - Overridden +================ +*/ +void hhProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { + Create( owner, start, dir.ToMat3() ); +} + +/* +================ +hhProjectile::Launch + +HUMANHEAD: AOBMERGE - Overridden +================ +*/ +void hhProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + Launch( start, dir.ToMat3(), pushVelocity, timeSinceFire, launchPower, dmgPower ); + + launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time +} + +/* +================ +hhProjectile::Create + +HUMANHEAD: AOBMERGE - Overridden +================ +*/ +void hhProjectile::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) { + Unbind(); + + SetOrigin( start ); + SetAxis( axis ); + + this->owner = owner; + + CreateLight( spawnArgs.GetString("mtr_light_shader"), spawnArgs.GetVector("light_size"), spawnArgs.GetVector("light_color", "1 1 1") * spawnArgs.GetFloat("light_intensity", "1.0"), spawnArgs.GetVector("light_offset"), spawnArgs.GetFloat("light_fadetime") ); + + GetPhysics()->SetContents( 0 ); + + state = CREATED; +} + +/* +================= +hhProjectile::Launch + +HUMANHEAD: aob - made second parameter idMat3& +================= +*/ +void hhProjectile::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + float fuse; + idVec3 velocity; + idAngles angular_velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + idVec3 gravVec; + + spawnArgs.GetVector( "velocity", "0 0 0", velocity ); + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); + + 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" ); + + 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.isLarge = spawnArgs.GetBool( "largeProjectile" ); // HUMANHEAD bjk + + if ( mass <= 0 ) { + gameLocal.Error( "Invalid mass on '%s'\n", GetClassname() ); + } + + thrust = mass * spawnArgs.GetFloat( "thrust" ); + thrust_start = SEC2MS( spawnArgs.GetFloat("thrust_start") ) + gameLocal.GetTime(); + thrust_end = SEC2MS( spawnArgs.GetFloat("thrust_duration") ) + thrust_start; + //HUMANHEAD: aob - if thrust is set then set TH_THINK + if( hhMath::Fabs(thrust) >= VECTOR_EPSILON ) { + BecomeActive( TH_THINK ); + } + //HUMANHEAD END + + if ( health ) { + fl.takedamage = true; + } + + gravVec = gameLocal.GetGravity(); + gravVec.NormalizeFast(); + + Unbind(); + + int contents = DetermineContents(); + int clipMask = DetermineClipmask(); + + //HUMANHEAD rww + launchQuat = axis.ToCQuat(); //save off launch orientation + launchPos = start; //save off launch pos + //HUMANHEAD END + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + //HUMANHEAD: aob - added DetermineClipModelOwner so some projectiles can decide to collide with there owners + physicsObj.GetClipModel()->SetOwner( DetermineClipModelOwner() ); + //HUMANHEAD END + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + if ( contact_friction == 0.0f ) { + physicsObj.NoContact(); + } + physicsObj.SetBouncyness( bounce ); + physicsObj.SetGravity( gravVec * gravity ); + physicsObj.SetContents( contents ); + physicsObj.SetClipMask( clipMask ); + physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] + pushVelocity ); + /* + if (gameLocal.isClient) { + gameLocal.Printf("C: (%f %f %f) (%f %f %f) (%f %f %f, %f %f %f, %f %f %f)\n", velocity[0], velocity[1], velocity[2], + pushVelocity[0], pushVelocity[1], pushVelocity[2], axis[0][0], axis[0][1], axis[0][2], axis[1][0], axis[1][1], + axis[1][2], axis[2][0], axis[2][1], axis[2][2]); + } + else { + gameLocal.Printf("S: (%f %f %f) (%f %f %f) (%f %f %f, %f %f %f, %f %f %f)\n", velocity[0], velocity[1], velocity[2], + pushVelocity[0], pushVelocity[1], pushVelocity[2], axis[0][0], axis[0][1], axis[0][2], axis[1][0], axis[1][1], + axis[1][2], axis[2][0], axis[2][1], axis[2][2]); + } + */ + + physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + SetPhysics( &physicsObj ); + + //HUMANHEAD rww - get rid of the thruster, needless projectile constructor overhead + //thruster.SetPosition( &physicsObj, 0, physicsObj.GetCenterOfMass() );//idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) ); + + //HUMANHEAD rww - debug projectile position and axis (for client side projectiles) + /* + extern void Debug_ClearDebugLines(void); + extern void Debug_AddDebugLine(idVec3 &start, idVec3 &end, int color); + + idVec3 p = start; + idVec3 prj; + Debug_ClearDebugLines(); + prj = p + (axis[0]*32.0f); + Debug_AddDebugLine(p, prj, 1); + prj = p + (axis[1]*32.0f); + Debug_AddDebugLine(p, prj, 2); + prj = p + (axis[2]*32.0f); + Debug_AddDebugLine(p, prj, 3); + */ + + if ( !gameLocal.isClient || fl.clientEntity ) //HUMANHEAD rww - if clientEntity + { + 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 ); + } + } + + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, true ); + + //HUMANHEAD: aob - replaces id's smoke_fly code + // CJR: Changed so that we can randomly choose between multiple fx_fly systems on a single projectile + BroadcastFxPrefixedRandom( "fx_fly", EV_SpawnFxFlyLocal ); + //HUMANHEAD END + + // 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; + } + + // HUMANHEAD CJR: if launched by a player, set the projectile's weapon index appropriately + if ( owner.IsValid() && owner->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast( owner.GetEntity() ); + weaponNum = player->GetCurrentWeapon(); + } else { // Otherwise, default the weaponNum to denote a non-player weapon + weaponNum = -1; + }// HUMANHEAD END + + state = LAUNCHED; + + if (gameLocal.isClient) { //HUMANHEAD rww + launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time + return; + } + + // Notify the AI about this launch + gameLocal.SendMessageAI( this, GetOrigin(), spawnArgs.GetFloat("ai_notify_launch", "0"), MA_OnProjectileLaunch ); + BroadcastEntityDef( spawnArgs.GetString("def_driver"), EV_SpawnDriverLocal ); + + launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time +} + +/* +================ +hhProjectile::DetermineClipModelOwner + +HUMANHEAD: aob +================ +*/ +idEntity* hhProjectile::DetermineClipModelOwner() { + return (spawnArgs.GetBool("collideWithOwner")) ? this : owner.GetEntity(); +} + +/* +================ +hhProjectile::Collide +================ +*/ +extern const int RB_VELOCITY_EXPONENT_BITS; //HUMANHEAD rww +extern const int RB_VELOCITY_MANTISSA_BITS; //HUMANHEAD rww +bool hhProjectile::Collide( const trace_t& collision, const idVec3& velocity ) { + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk + return false; + } + + // HUMANHEAD CJR: Don't allow the collision if the projectile has recently hit a portal + if ( collidedPortal.IsValid() ) { + return false; + } // HUMANHEAD END + + if ( gameLocal.isClient ) { + //HUMANHEAD rww - our projectile stuff is pretty different from id's at this point, so i'm just making some new prediction code. + if (state == EXPLODED || state == FIZZLED || state == COLLIDED) { //HUMANHEAD bjk + ProcessCollision(&collision, velocity); + return false; + } + //HUMANHEAD END + } + + //HUMANHEAD rww - commented out to allow projectiles to collide with owner after portalling + /* + if (gameLocal.isClient) { //HUMANHEAD rww + idEntity* entHit = gameLocal.GetTraceEntity(collision); + if ( entHit == owner.GetEntity() ) { + return true; + } + } + else { + // get the entity the projectile collided with + idEntity* entHit = gameLocal.GetTraceEntity(collision); + if ( entHit == owner.GetEntity() ) { + assert( 0 ); + return false; + } + } + */ + + // remove projectile when a 'noimpact' surface is hit + if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + common->DPrintf("removing projectile that hit noimpact surface: mat=[%s]\n", collision.c.material->GetName() ); + RemoveProjectile( 0 ); + return false; + } + + //HUMANHEAD rww - send events for collisions on the server for pseudo-sync'd projectiles + if (gameLocal.isServer && fl.networkSync && !netSyncPhysics) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + + msg.WriteBits(collision.c.contents, 32); + msg.WriteFloat(collision.c.dist); + msg.WriteBits(collision.c.entityNum, GENTITYNUM_BITS); + msg.WriteShort(collision.c.id); + if (!collision.c.material) { + msg.WriteShort(-1); + } + else { + msg.WriteShort(collision.c.material->Index()); + } + msg.WriteBits(collision.c.modelFeature, 32); + //ensure it is normalized properly first + idVec3 normal = collision.c.normal; + normal.Normalize(); + msg.WriteDir(normal, 24); + msg.WriteFloat(collision.c.point.x); + msg.WriteFloat(collision.c.point.y); + msg.WriteFloat(collision.c.point.z); + msg.WriteBits(collision.c.trmFeature, 32); + msg.WriteBits(collision.c.type, 4); + msg.WriteFloat(collision.fraction); + + //unfortunately, this is needed for proper decal projections + msg.WriteFloat(velocity.x, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); + msg.WriteFloat(velocity.y, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); + msg.WriteFloat(velocity.z, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); + + msg.WriteDir(collision.endAxis[0], 24); + + ServerSendPVSEvent(EVENT_PROJECTILE_EXPLOSION, &msg, collision.endpos); + } + //HUMANHEAD END + + //HUMANHEAD rww - our projectile stuff is pretty different from id's at this point, so i'm just making some new prediction code. + if (gameLocal.isClient) { + //ProcessCollision(&collision, velocity); + if (fl.networkSync && !netSyncPhysics) { //if this projectile is psuedo-sync'd, we will be expecting impact results from the server. + ClientHideProjectile(); + return 0; + } + return ProcessCollisionEvent( &collision, velocity ); + } + //HUMANHEAD END + + if ( !gameLocal.isMultiplayer && spawnArgs.GetString( "hit_notify" ) && owner.IsValid() && owner->IsType( hhMonsterAI::Type ) ) { + gameLocal.SendMessageAI( this, GetOrigin(), spawnArgs.GetFloat("ai_notify_yes", "0"), MA_OnProjectileHit ); + } + + return ProcessCollisionEvent( &collision, velocity ); +} + +/* +================ +hhProjectile::ProcessCollisionEvent +================ +*/ +bool hhProjectile::ProcessCollisionEvent( const trace_t* collision, const idVec3& velocity ) { + assert( collision ); + + idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; + const idEventDef* eventDef = matterEventsCollision.GetPartner( ent, collision->c.material ); + assert( eventDef ); + + ProcessEvent( eventDef, collision, velocity ); + return gameLocal.program.GetReturnedBool(); +} + +/* +================ +hhProjectile::ProcessCollision + +HUMANHEAD: aob +================ +*/ +int hhProjectile::ProcessCollision( const trace_t* collision, const idVec3& velocity ) { + PROFILE_SCOPE("ProjectileCollision", PROFMASK_PHYSICS|PROFMASK_COMBAT); + idEntity* entHit = gameLocal.entities[ collision->c.entityNum ]; + + SetOrigin( collision->endpos ); + SetAxis( collision->endAxis ); + + if( fxFly.IsValid() ) { + fxFly->Stop(); + } + SAFE_REMOVE( fxFly ); + FreeLightDef(); + CancelEvents( &EV_Fizzle ); + + if (entHit) { //rww - may be null on client. + if (!gameLocal.isClient || (fl.networkSync && !netSyncPhysics)) { //don't do this on the client, unless this is a sync'd projectile + DamageEntityHit( collision, velocity, entHit ); + } + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + surfTypes_t matterType = gameLocal.GetMatterType( entHit, collision->c.material, "hhProjectile::ProcessCollision" ); + return PlayImpactSound( gameLocal.FindEntityDefDict(spawnArgs.GetString("def_damage")), collision->endpos, matterType ); +} + +/* +================ +hhProjectile::DamageEntityHit + +HUMANHEAD: aob +================ +*/ +void hhProjectile::DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ) { + PROFILE_SCOPE("DamageEntityHit", PROFMASK_COMBAT); + if (GERMAN_VERSION || g_nogore.GetBool()) { + if (entHit->IsType(idActor::Type)) { + idActor *actor = reinterpret_cast (entHit); + if ( !actor->fl.takedamage || ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) ) { + // Don't process hits on ragdolls + return; + } + } + } + + float push = 0.0f; + float damageScale = 1.0f; + const char *damage = NULL; + if (gameLocal.isMultiplayer) { //rww - check for special mp damage def + damage = spawnArgs.GetString( "def_damage_mp" ); + } + if (!damage || !damage[0]) { + damage = spawnArgs.GetString( "def_damage" ); + } + hhPlayer* playerHit = (entHit->IsType(hhPlayer::Type)) ? static_cast(entHit) : NULL; + idAFEntity_Base* afHit = (entHit->IsType(idAFEntity_Base::Type)) ? static_cast(entHit) : NULL; + + idVec3 dir = velocity.ToNormal(); + + // non-radius damage defs can also apply an additional impulse to the rigid body physics impulse + const idDeclEntityDef *def = gameLocal.FindEntityDef( damage, false ); + if ( def ) { + if (entHit->IsType(hhProjectile::Type)) { + push = 0.0f; // mdl: Don't let projectiles push each other + } else if (afHit && afHit->IsActiveAF() ) { + push = def->dict.GetFloat( "push_ragdoll" ); + } else { + push = def->dict.GetFloat( "push" ); + } + } + + if (!gameLocal.isClient) { //rww + if( playerHit ) { + // pdm: save collision location in case we want to project a blob there + playerHit->playerView.SetDamageLoc( collision->endpos ); + } + + if( DamageIsValid(collision, damageScale) && entHit->fl.takedamage ) { + UpdateBalanceInfo( collision, entHit ); + + if( damage && damage[0] ) { + idEntity *killer = owner.GetEntity(); + if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people + hhVehicle *veh = static_cast(killer); + if (veh->GetPilot()) { + killer = veh->GetPilot(); + } + } + + entHit->Damage( this, killer, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(collision->c.id) ); + + if ( playerHit && def->dict.GetInt( "freeze_duration" ) > 0 ) { + playerHit->Freeze( def->dict.GetInt( "freeze_duration" ) ); + } + } + } + + // HUMANHEAD bjk: moved to after damage so impulse can be applied to ragdoll + if ( push > 0.0f ) { + if (g_debugImpulse.GetBool()) { + gameRenderWorld->DebugArrow(colorYellow, collision->c.point, collision->c.point + (push*dir), 25, 2000); + } + + entHit->ApplyImpulse( this, collision->c.id, collision->c.point, push * dir ); + } + } + + if ( entHit->fl.applyDamageEffects ) { + ApplyDamageEffect( entHit, collision, velocity, damage ); + } +} + +/* +================ +hhProjectile::DamageIsValid + +HUMANHEAD: aob +================ +*/ +bool hhProjectile::DamageIsValid( const trace_t* collision, float& damageScale ) { + damageScale = DetermineDamageScale( collision ); + + return true; +} + +/* +================ +hhProjectile::ApplyDamageEffect + +HUMANHEAD: aob +================ +*/ +void hhProjectile::ApplyDamageEffect( idEntity* hitEnt, const trace_t* collision, const idVec3& velocity, const char* damageDefName ) { + if( hitEnt ) { + hitEnt->AddDamageEffect( *collision, velocity, damageDefName, (!fl.networkSync || netSyncPhysics) ); + } +} + +int hhProjectile::DetermineContents() { + return (spawnArgs.GetBool("proj_collision")) ? CONTENTS_PROJECTILE | CONTENTS_SHOOTABLE : CONTENTS_PROJECTILE; +} + +int hhProjectile::DetermineClipmask() { + return MASK_SHOT_RENDERMODEL; +} + +/* +================ +hhProjectile::UpdateBalanceInfo + +HUMANHEAD: aob +================ +*/ +void hhProjectile::UpdateBalanceInfo( const trace_t* collision, const idEntity* hitEnt ) { + hhPlayer* player = (owner.IsValid() && owner->IsType(hhPlayer::Type)) ? static_cast(owner.GetEntity()) : NULL; + + if( hitEnt->IsType(idActor::Type) && player ) { + player->SetLastHitTime( gameLocal.GetTime() ); + player->AddProjectileHits( 1 ); + } +} + +/* +================ +hhProjectile::SpawnProjectile + +HUMANHEAD: aob +================ +*/ +hhProjectile* hhProjectile::SpawnProjectile( const idDict* args ) {//FIXME: Broadcast + assert( args ); + assert(!gameLocal.isClient); + + idEntity* ent = NULL; + + gameLocal.SpawnEntityDef( *args, &ent ); + HH_ASSERT( ent && ent->IsType(hhProjectile::Type) ); + + return static_cast(ent); +} + +/* +================ +hhProjectile::SpawnClientProjectile + +HUMANHEAD: rww +================ +*/ +hhProjectile* hhProjectile::SpawnClientProjectile( const idDict* args ) {//FIXME: Broadcast + assert( args ); + + idEntity* ent = NULL; + + gameLocal.SpawnEntityDef( *args, &ent, true, gameLocal.isClient ); + HH_ASSERT( ent && ent->IsType(hhProjectile::Type) ); + + ent->fl.networkSync = false; + ent->fl.clientEvents = true; + + return static_cast(ent); +} + +/* +================ +hhProjectile::Killed +================ +*/ +void hhProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //HUMANHEAD: aob - added collision so we can get explosion to show up when killed + trace_t collision; + + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + memset( &collision, 0, sizeof( trace_t ) ); + collision.fraction = 0.0f; + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.entityNum = attacker->entityNumber; + collision.c.normal = inflictor->GetOrigin() - GetOrigin(); + collision.c.normal.Normalize(); + Explode( &collision, GetPhysics()->GetLinearVelocity(), 0 ); + } else { + Fizzle(); + } +} + +/* +================ +hhProjectile::Fizzle +================ +*/ +void hhProjectile::Fizzle( void ) { + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk + return; + } + + int removeTime = StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, true ); + + if (!gameLocal.isClient) + { + // fizzle FX + hhFxInfo fxInfo; + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.RemoveWhenDone( true ); + BroadcastFxInfoPrefixed( "fx_fuse", GetOrigin(), GetAxis(), &fxInfo ); + } + + SAFE_REMOVE( fxFly ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + physicsObj.PutToRest(); + + FreeLightDef(); + + state = FIZZLED; + + RemoveProjectile( removeTime ); +} + +/* +================= +hhProjectile::RemoveProjectile +================= +*/ +void hhProjectile::RemoveProjectile( const int removeDelay ) { + Hide(); + BecomeInactive( TH_TICKER|TH_THINK ); + RemoveBinds();//Remove any fx we have because they aren't hidden + PostEventMS( &EV_Remove, removeDelay ); +} + +/* +================ +hhProjectile::Explode +================ +*/ +void hhProjectile::Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ) { + const char *fxname = NULL; + int length = 0; + + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk + return; + } + + if( !collision ) { + return; + } + + //HUMANHEAD: aob + StartSound( "snd_explode", SND_CHANNEL_BODY, 0, true, &length ); + removeDelay = hhMath::hhMax( length, removeDelay ); + + // explosion light + //FIXME: may need to be Broadcast + removeDelay = hhMath::hhMax( removeDelay, CreateLight(spawnArgs.GetString("mtr_explode_light_shader"), spawnArgs.GetVector("explode_light_size"), spawnArgs.GetVector("explode_light_color", "1 1 1") * spawnArgs.GetFloat("explode_light_intensity", "1.0"), spawnArgs.GetVector("explode_light_offset"), spawnArgs.GetFloat("explode_light_fadetime")) ); + + //HUMANHEAD: aob - moved logic to helper function + SpawnExplosionFx( collision ); + + SpawnDebris( collision->c.normal, velocity.ToNormal() ); + //HUMANHEAD END + + state = EXPLODED; + + //HUMANHEAD rww + if (!gameLocal.isClient) { + idEntity *killer = owner.GetEntity(); + if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people + hhVehicle *veh = static_cast(killer); + if (veh->GetPilot()) { + killer = veh->GetPilot(); + } + } + + // splash damage + if (!gameLocal.isClient) { + //SplashDamage( collision->endpos, killer, this, this, spawnArgs.GetString("def_splash_damage") ); + SplashDamage( GetOrigin(), killer, this, this, spawnArgs.GetString("def_splash_damage") ); + } + } + //HUMANHEAD END + + //HUMANHEAD: aob - moved logic to helper function + RemoveProjectile( removeDelay ); + //HUMANHEAD END +} + +/* +================ +hhProjectile::SplashDamage + +HUMANHEAD: aob +================ +*/ +void hhProjectile::SplashDamage( const idVec3& origin, idEntity* attacker, idEntity* ignoreDamage, idEntity* ignorePush, const char* splashDamageDefName ) { + if( splashDamageDefName && splashDamageDefName[0] ) { + gameLocal.RadiusDamage( origin, this, attacker, ignoreDamage, ignorePush, splashDamageDefName ); + } +} + +/* +================ +hhProjectile::SpawnExplosionFx + +HUMANHEAD: aob +================ +*/ +void hhProjectile::SpawnExplosionFx( const trace_t* collision ) { + idEntity* hitEnt = NULL; + hhFxInfo fxInfo; + const idDict* dict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_detonateFx"), false ); + + if( !dict || !collision || collision->fraction >= 1.0f ) { + return; + } + + hitEnt = gameLocal.entities[ collision->c.entityNum ]; + + fxInfo.SetNormal( collision->c.normal ); + fxInfo.SetIncomingVector( GetAxis()[0] ); + fxInfo.SetBounceVector( hhProjectile::GetBounceDirection( physicsObj.GetLinearVelocity(), + collision->c.normal, + this, hitEnt + ) ); + fxInfo.RemoveWhenDone( true ); + surfTypes_t matterType = gameLocal.GetMatterType( hitEnt, collision->c.material, "hhProjectile::SpawnExplosionFx" ); + const char* fxKey = gameLocal.MatterTypeToMatterKey( "fx", matterType ); + BroadcastFxInfo( dict->RandomPrefix(fxKey, gameLocal.random), GetOrigin(), GetAxis(), &fxInfo, NULL, false ); //rww - no broadcast, only locally. + //FIXME: Once BroadcastFxInfo() is gone and we are spawning the entityfx directly, mark it as fl.neverdormant here +} + +/* +================ +hhProjectile::SpawnDebris + +HUMANHEAD: aob +================ +*/ +void hhProjectile::SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ) { + static char debrisKey[] = "def_debris"; + static char countKey[] = "debris_count"; + static char spreadKey[] = "debris_spread"; + + idDebris *debris = NULL; + idEntity *ent = NULL; + idStr indexStr; + int amount = 0; + const idDict *dict = NULL; + const idKeyValue* defKV = NULL; + for( defKV = spawnArgs.MatchPrefix(debrisKey, NULL); defKV; defKV = spawnArgs.MatchPrefix(debrisKey, defKV) ) { + + if( !defKV->GetValue().Length() ) { + continue; + } + + dict = gameLocal.FindEntityDefDict( defKV->GetValue().c_str(), false ); + if( !dict ) { + continue; + } + + indexStr = defKV->GetKey(); + indexStr.Strip( debrisKey ); + + if (gameLocal.isMultiplayer) { //rww - for decreasing the count for things in mp on a design basis + amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s_mp%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); + if (!amount) { + amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); + } + } + else { + amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); + } + for ( int i = 0; i < amount; i++ ) { + //HUMANHEAD: aob + idVec3 dir = hhUtils::RandomSpreadDir( collisionNormal.ToMat3(), DEG2RAD(spawnArgs.GetFloat(va("%s%s", spreadKey, indexStr.c_str()))) ); + //HUMAMHEAD END + + gameLocal.SpawnEntityDef( *dict, &ent, true, gameLocal.isClient ); //HUMANHEAD rww - make them local non-broadcast entities. + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "hhProjectile: 'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( owner.GetEntity(), GetOrigin() + collisionNormal*10, dir.ToMat3() ); // HUMANHEAD bjk: displace out of surface + debris->Launch(); + debris->fl.networkSync = false; //HUMANHEAD rww + debris->fl.clientEvents = true; //HUMANHEAD rww + } + } +} + +/* +================ +hhProjectile::SetGravity +================ +*/ +void hhProjectile::SetGravity( const idVec3 &newGravity ) { + float relativeMagnitude = spawnArgs.GetFloat( "gravity" ); + idVec3 newGravityVector( vec3_zero ); + + if( GetGravity().Compare(newGravity, VECTOR_EPSILON) ) { + return; + } + + if( relativeMagnitude > 0.0f ) { + newGravityVector = newGravity; + relativeMagnitude *= newGravityVector.Normalize() / gameLocal.GetGravity().Length(); + newGravityVector *= relativeMagnitude; + } + + GetPhysics()->SetGravity( newGravityVector ); +} + +/* +================ +hhProjectile::ProcessAllowCollisionEvent +================ +*/ +bool hhProjectile::ProcessAllowCollisionEvent( const trace_t* collision ) { + assert( collision ); + + idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; + const idEventDef* eventDef = matterEventsAllowCollision.GetPartner( ent, collision->c.material ); + assert( eventDef ); + + ProcessEvent( eventDef, collision ); + return gameLocal.program.GetReturnedBool(); +} + +//============================================================================= +// +// hhProjectile::Portalled +// +// The projectile was just portalled. Update the fx info to the bound fly fx system +// HUMANHEAD CJR +//============================================================================= + +void hhProjectile::Portalled(idEntity *portal) { + if ( fxFly.IsValid() ) { + hhFxInfo fxInfo; + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + + fxFly->SetFxInfo( fxInfo ); + + // Reset the fx system + fxFly->Stop(); + fxFly->Start( gameLocal.time ); + } + if (physicsObj.GetClipModel()) { //HUMANHEAD rww - allow projectiles to collide with owner after portalling + physicsObj.GetClipModel()->SetOwner(this); + } +} // END HUMANHEAD + +//============================================================================= +// +// hhProjectile::AllowCollision +// +// Determines if the projectile can strike a given entity. Here, all +// projectiles will pass-through Wraiths (other than arrows, which is handled +// in the arrow code) +//============================================================================= +bool hhProjectile::AllowCollision( const trace_t& collision ) { + return ProcessAllowCollisionEvent( &collision ); +} + +/* +================ +hhProjectile::Event_Fuse_Explode +================ +*/ +void hhProjectile::Event_Fuse_Explode() { + trace_t collision; + + SIMDProcessor->Memset( &collision, 0, sizeof(trace_t) ); + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.normal = -gameLocal.GetGravityNormal(); + Explode( &collision, GetPhysics()->GetLinearVelocity(), 0 ); +} + +/* +================ +hhProjectile::Event_Collision_Explode +================ +*/ +void hhProjectile::Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ) { + Explode( collision, velocity, ProcessCollision(collision, velocity) ); + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_Collision_Impact +================ +*/ +void hhProjectile::Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ) { + CancelEvents( &EV_Explode ); + RemoveProjectile( ProcessCollision(collision, velocity) ); + state = COLLIDED; + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_Collision_DisturbLiquid +================ +*/ +void hhProjectile::Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3& velocity ) { + CancelEvents( &EV_Explode ); + RemoveProjectile( ProcessCollision(collision, velocity) ); + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_Collision_Remove +================ +*/ +void hhProjectile::Event_Collision_Remove( const trace_t* collision, const idVec3& velocity ) { + RemoveProjectile( 0 ); + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_AllowCollision_CollideNoProj +================ +*/ +void hhProjectile::Event_AllowCollision_CollideNoProj( const trace_t* collision ) { + idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; + if( ent->IsType( hhProjectile::Type ) ) { + if ( static_cast(ent)->ProjCollide() ) { + idThread::ReturnInt( 1 ); + return; + } + } + + if( ent->IsType( hhProjectile::Type ) && // If we're colliding with another projectile + ( !bNoCollideWithCrawlers || // AND we're not set to collide with crawlers + !( ent->IsType( hhProjectileRocketLauncher::Type ) && // OR we're not a rocket launcher projectile + ent->IsType( hhProjectileCrawlerGrenade::Type ) ) ) ) { // AND we're not a crawler projectile + idThread::ReturnInt( 0 ); // Pass through + return; + } + + hhProjectile::Event_AllowCollision_Collide( collision ); +} + +/* +================ +hhProjectile::Event_AllowCollision_Collide +================ +*/ +void hhProjectile::Event_AllowCollision_Collide( const trace_t* collision ) { + idThread::ReturnInt( 1 ); +} + +/* +================ +hhProjectile::Event_AllowCollision_PassThru +================ +*/ +void hhProjectile::Event_AllowCollision_PassThru( const trace_t* collision ) { + idThread::ReturnInt( 0 ); +} + +/* +================ +hhProjectile::Event_SpawnDriverLocal + +HUMANHEAD: aob +================ +*/ +void hhProjectile::Event_SpawnDriverLocal( const char* defName ) { + if( !defName || !defName[ 0 ] ) { + return; + } + + driver = static_cast( gameLocal.SpawnObject(defName) ); + driver->SetPassenger( this ); + + //Not sure if this should be pulled out of the def_driver dict or not + float roll = hhMath::Lerp( spawnArgs.GetVec2("driver_rollRange"), gameLocal.random.RandomFloat() ); + driver->SetAxis( idAngles( 0.0f, 0.0f, roll ).ToMat3() * driver->GetAxis() ); +} + +/* +================ +hhProjectile::Event_SpawnFxFlyLocal + +HUMANHEAD: aob +================ +*/ +void hhProjectile::Event_SpawnFxFlyLocal( const char* defName ) { + if( !defName || !defName[0] ) { + return; + } + + SAFE_REMOVE(fxFly); + hhFxInfo fxInfo; + + fxInfo.SetNormal( -GetAxis()[0] ); + fxInfo.SetEntity( this ); + fxInfo.RemoveWhenDone( false ); + fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, true ); //rww - client (local) entity. + if (fxFly.IsValid()) { + fxFly->fl.neverDormant = true; + + //rww + fxFly->fl.networkSync = false; + fxFly->fl.clientEvents = true; + } +} + +//================ +//hhProjectile::Save +//================ +void hhProjectile::Save( idSaveGame *savefile ) const { + driver.Save( savefile ); + fxFly.Save( savefile ); + savefile->WriteInt( thrust_start ); + savefile->WriteBool( bDDACounted ); + parentProjectile.Save( savefile ); + savefile->WriteInt( launchTimestamp ); + savefile->WriteInt( weaponNum ); + savefile->WriteBool( bPlayFlyBySound ); + savefile->WriteFloat( flyBySoundDistSq ); + + collidedPortal.Save( savefile ); + savefile->WriteVec3( collideLocation ); + savefile->WriteVec3( collideVelocity ); +} + +//================ +//hhProjectile::Restore +//================ +void hhProjectile::Restore( idRestoreGame *savefile ) { + driver.Restore( savefile ); + fxFly.Restore( savefile ); + savefile->ReadInt( thrust_start ); + savefile->ReadBool( bDDACounted ); + parentProjectile.Restore( savefile ); + savefile->ReadInt( launchTimestamp ); + savefile->ReadInt( weaponNum ); + savefile->ReadBool( bPlayFlyBySound ); + savefile->ReadFloat( flyBySoundDistSq ); + + bNoCollideWithCrawlers = spawnArgs.GetBool( "noCollideWithCrawlers", "0" ); + bProjCollide = spawnArgs.GetBool( "proj_collision", "0" ); + + collidedPortal.Restore( savefile ); + savefile->ReadVec3( collideLocation ); + savefile->ReadVec3( collideVelocity ); +} + +/* +================ +hhProjectile::WriteToSnapshot +================ +*/ +void hhProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { + //rww - we capture the launch orientation/pos for predicting the projectile launch, and (usually) don't sync physics at all + if (fabsf(launchQuat.ToAngles().roll) > 0.001f) { //is it going to translate to a direction happily, or do we need a real orientation? + msg.WriteBits(1, 1); + msg.WriteFloat(launchQuat.x); + msg.WriteFloat(launchQuat.y); + msg.WriteFloat(launchQuat.z); + } + else { + msg.WriteBits(0, 1); + msg.WriteDir(launchQuat.ToMat3()[0], 24); + } + msg.WriteFloat(launchPos.x); + msg.WriteFloat(launchPos.y); + msg.WriteFloat(launchPos.z); + + idProjectile::WriteToSnapshot(msg); +} + +/* +================ +hhProjectile::ReadFromSnapshot +================ +*/ +void hhProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //rww - we capture the launch orientation for predicting the projectile launch, and (usually) don't sync physics at all + bool fullOrientation = !!msg.ReadBits(1); + if (fullOrientation) { + launchQuat.x = msg.ReadFloat(); + launchQuat.y = msg.ReadFloat(); + launchQuat.z = msg.ReadFloat(); + } + else { + idVec3 dir = msg.ReadDir(24); + launchQuat = dir.ToMat3().ToCQuat(); + } + launchPos.x = msg.ReadFloat(); + launchPos.y = msg.ReadFloat(); + launchPos.z = msg.ReadFloat(); + + idProjectile::ReadFromSnapshot(msg); +} + +/* +================ +hhProjectile::ClientHideProjectile +================ +*/ +void hhProjectile::ClientHideProjectile(void) { + Hide(); + FreeLightDef(); + BecomeInactive(TH_THINK|TH_PHYSICS); + + GetPhysics()->PutToRest(); + + if (fxFly.IsValid()) { + fxFly->Stop(); + SAFE_REMOVE( fxFly ); + } +} + +/* +=============== +hhProjectile::GetBounceDirection +=============== +*/ + +idVec3 hhProjectile::GetBounceDirection( const idVec3 &incoming, + const idVec3 &surface_normal, + const idEntity *incoming_entity, + const idEntity *surface_entity ) { + idVec3 bounceDir; + idVec3 tanget; + idVec3 normal; + float dot = 0.0f; + float eProjectile = 1.0f; // Elasticity constants + float eTarget = 1.0f; + float fProjectile = 0.0f; // Friction constants + float fTarget = 0.0f; + + if ( incoming_entity ) { + eProjectile = incoming_entity->spawnArgs.GetFloat("bounce", "1"); + fProjectile = incoming_entity->spawnArgs.GetFloat("contact_friction", "0"); + } + + if ( surface_entity ) { + eTarget = surface_entity->spawnArgs.GetFloat("bounce", "1"); + fTarget = surface_entity->spawnArgs.GetFloat("contact_friction", "0"); + } + + dot = incoming * surface_normal; + normal = surface_normal * dot; + tanget = incoming - normal; + + bounceDir = tanget * (1.0f - fProjectile) * (1.0f - fTarget) - + normal * (eProjectile * eTarget); + + //? Should this all be seperated out? Ie, what if they don't want it normalized? + if ( bounceDir.Length() < .0001f ) { + bounceDir = surface_normal; + } + + //HUMANHEAD: nla/aob - removed normalize to give return value more dependence on inputs. + + return( bounceDir ); +} + diff --git a/neo/prey/Prey/prey_projectile.h b/neo/prey/Prey/prey_projectile.h new file mode 100644 index 00000000..fba1f513 --- /dev/null +++ b/neo/prey/Prey/prey_projectile.h @@ -0,0 +1,176 @@ +#ifndef __PREY_PROJECTILE_H__ +#define __PREY_PROJECTILE_H__ + +class hhBeamSystem; + +extern const idEventDef EV_SpawnDriverLocal; +extern const idEventDef EV_SpawnFxFlyLocal; + +extern const idEventDef EV_Collision_Flesh; +extern const idEventDef EV_Collision_Metal; +extern const idEventDef EV_Collision_AltMetal; +extern const idEventDef EV_Collision_Wood; +extern const idEventDef EV_Collision_Stone; +extern const idEventDef EV_Collision_Glass; +extern const idEventDef EV_Collision_Liquid; +extern const idEventDef EV_Collision_Spirit; +extern const idEventDef EV_Collision_Remove; +extern const idEventDef EV_Collision_CardBoard; +extern const idEventDef EV_Collision_Tile; +extern const idEventDef EV_Collision_Forcefield; +extern const idEventDef EV_Collision_Wallwalk; +extern const idEventDef EV_Collision_Chaff; +extern const idEventDef EV_Collision_Pipe; + + +extern const idEventDef EV_AllowCollision_Flesh; +extern const idEventDef EV_AllowCollision_Metal; +extern const idEventDef EV_AllowCollision_AltMetal; +extern const idEventDef EV_AllowCollision_Wood; +extern const idEventDef EV_AllowCollision_Stone; +extern const idEventDef EV_AllowCollision_Glass; +extern const idEventDef EV_AllowCollision_Liquid; +extern const idEventDef EV_AllowCollision_Spirit; +extern const idEventDef EV_AllowCollision_CardBoard; +extern const idEventDef EV_AllowCollision_Tile; +extern const idEventDef EV_AllowCollision_Forcefield; +extern const idEventDef EV_AllowCollision_Wallwalk; +extern const idEventDef EV_AllowCollision_Chaff; +extern const idEventDef EV_AllowCollision_Pipe; + + +/*********************************************************************** + + hhProjectile + +***********************************************************************/ +class hhPortal; // cjr + +class hhProjectile : public idProjectile { + CLASS_PROTOTYPE( hhProjectile ); + + public: + void Spawn(); + virtual ~hhProjectile(); + virtual void Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ); + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void SetOrigin( const idVec3& origin ); + virtual void SetAxis( const idMat3& axis ); + + virtual void Think(); + + virtual void RemoveProjectile( const int removeDelay ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool ProcessCollisionEvent( const trace_t* collision, const idVec3& velocity ); + virtual void Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ); + virtual void SplashDamage( const idVec3& origin, idEntity* attacker, idEntity* ignoreDamage, idEntity* ignorePush, const char* splashDamageDefName ); + virtual void BounceSplat( const idVec3& origin, const idVec3& dir ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Fizzle( void ); + + virtual void SetGravity( const idVec3 &newGravity ); + virtual const idVec3& GetGravity() const { return idEntity::GetGravity(); } + + virtual bool GetDDACounted() const { return bDDACounted; } + virtual void SetDDACounted() { bDDACounted = true; } + virtual void SetParentProjectile( hhProjectile* in_parent ); + virtual hhProjectile* GetParentProjectile( void ); + virtual int GetLaunchTimestamp() const { return launchTimestamp; } + + virtual void Portalled(idEntity *portal); + virtual bool AllowCollision( const trace_t &collision ); + + virtual void UpdateBalanceInfo( const trace_t* collision, const idEntity* hitEnt ); + + static hhProjectile* SpawnProjectile( const idDict* args ); + static hhProjectile* SpawnClientProjectile( const idDict* args ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientHideProjectile(void); + + virtual const int GetWeaponNum( void ) const { return weaponNum; } // HUMANHEAD CJR: weapon-specific index on each projectile + bool ProjCollide() { return bProjCollide; } + + virtual int ProcessCollision( const trace_t* collision, const idVec3& velocity ); //rww - made public + + void SetCollidedPortal( hhPortal *newPortal, idVec3 newLocation, idVec3 newVelocity ); // cjr + + static idVec3 GetBounceDirection( const idVec3 &incoming_vector, + const idVec3 &surface_normal, + const idEntity *incoming_entity = NULL, + const idEntity *surface_entity = NULL ); + + idCQuat launchQuat; //rww - for saving off launch orientation + idVec3 launchPos; //rww - for saving off launch pos + + protected: + virtual float DetermineDamageScale( const trace_t* collision ) const { return 1.0f; } + virtual bool DamageIsValid( const trace_t* collision, float& damageScale ); + virtual int DetermineContents(); + virtual int DetermineClipmask(); + + virtual idEntity* DetermineClipModelOwner(); + + virtual int PlayImpactSound( const idDict* dict, const idVec3 &origin, surfTypes_t type ); + + virtual void UpdateLight(); + virtual void UpdateLightPosition(); + virtual void UpdateLightFade(); + virtual int CreateLight( const char* shaderName, const idVec3& size, const idVec3& color, const idVec3& offset, float fadeTime ); + + virtual void ApplyDamageEffect( idEntity* entHit, const trace_t* collision, const idVec3& velocity, const char* damageDefName ); + + virtual void SpawnExplosionFx( const trace_t* collision ); + virtual void SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ); + + virtual void DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ); + + virtual bool ProcessAllowCollisionEvent( const trace_t* collision ); + + protected: + void Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_Remove( const trace_t* collision, const idVec3& velocity ); + void Event_AllowCollision_CollideNoProj( const trace_t* collision ); + + void Event_AllowCollision_Collide( const trace_t* collision ); + void Event_AllowCollision_PassThru( const trace_t* collision ); + + void Event_Fuse_Explode(); + + void Event_SpawnDriverLocal( const char* defName ); + void Event_SpawnFxFlyLocal( const char* defName ); + + + protected: + idEntityPtr driver; + idEntityPtr fxFly; + int thrust_start; + bool bDDACounted; //already counted by dda as hitting something + idEntityPtr parentProjectile; //projectile that spawned me + int launchTimestamp; + + int weaponNum; // cjr - weapon index that spawned this projectile (-1) for non-player weapons + + bool bNoCollideWithCrawlers; // mdl: Defines whether or not we collide with crawlers/rockets + bool bProjCollide; + + // jsh flyby sounds + float flyBySoundDistSq; + bool bPlayFlyBySound; + + idEntityPtr collidedPortal; // cjr: This projectile struck a portal, so it should get portalled before thinking + idVec3 collideLocation; // cjr: This projectile struck a portal, so it should get portalled before thinking + idVec3 collideVelocity; // cjr: This projectile struck a portal, so it should get portalled before thinking +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectileautocannon.cpp b/neo/prey/Prey/prey_projectileautocannon.cpp new file mode 100644 index 00000000..6874d5a3 --- /dev/null +++ b/neo/prey/Prey/prey_projectileautocannon.cpp @@ -0,0 +1,21 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileAutoCannonGrenade ) + EVENT( EV_Collision_Flesh, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileAutoCannonGrenade::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileAutoCannonGrenade::Event_AllowCollision_Collide ) +END_CLASS \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectileautocannon.h b/neo/prey/Prey/prey_projectileautocannon.h new file mode 100644 index 00000000..09270499 --- /dev/null +++ b/neo/prey/Prey/prey_projectileautocannon.h @@ -0,0 +1,8 @@ +#ifndef __HH_PROJECTILE_AUTOCANNON_GRENADE_H +#define __HH_PROJECTILE_AUTOCANNON_GRENADE_H + +class hhProjectileAutoCannonGrenade : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileAutoCannonGrenade ) +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilebounce.cpp b/neo/prey/Prey/prey_projectilebounce.cpp new file mode 100644 index 00000000..43d6a5fe --- /dev/null +++ b/neo/prey/Prey/prey_projectilebounce.cpp @@ -0,0 +1,34 @@ +#include "precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileBounce ) + EVENT( EV_Collision_Flesh, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Metal, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileBounce::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileBounce::Event_Collision_Bounce ) + +END_CLASS + +void hhProjectileBounce::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + if( !collision || collision->fraction == 1.0f ) { + return; + } + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, true, NULL ); + float dot = velocity * collision->c.normal; + idVec3 normal = collision->c.normal * dot; + idVec3 tangent = (velocity - normal).ToNormal() - normal.ToNormal(); + idVec3 newVelocity = tangent.ToNormal() * velocity.Length(); + physicsObj.SetLinearVelocity( newVelocity ); + idThread::ReturnInt( 0 ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilebounce.h b/neo/prey/Prey/prey_projectilebounce.h new file mode 100644 index 00000000..609d03e3 --- /dev/null +++ b/neo/prey/Prey/prey_projectilebounce.h @@ -0,0 +1,11 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_BOUNCE_H +#define __HH_PROJECTILE_BOUNCE_H + +class hhProjectileBounce : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileBounce ); + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilebug.cpp b/neo/prey/Prey/prey_projectilebug.cpp new file mode 100644 index 00000000..b1ea5990 --- /dev/null +++ b/neo/prey/Prey/prey_projectilebug.cpp @@ -0,0 +1,135 @@ +#include "precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +const idEventDef EV_Guide( "" ); + +CLASS_DECLARATION( hhProjectileTracking, hhProjectileBug ) + EVENT( EV_Collision_Flesh, hhProjectileBug::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Liquid, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Chaff, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileBug::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileBug::Event_Collision_Bounce ) + + EVENT( EV_Guide, hhProjectileBug::Event_TrackTarget ) +END_CLASS + +void hhProjectileBug::Spawn() { + enemyRadius = spawnArgs.GetFloat( "enemy_radius", "200" ); +} + +idEntity* hhProjectileBug::DetermineEnemy() { + idEntity * entityList[ MAX_GENTITIES ]; + idEntity* possibleEnemy = NULL; + float currentEnemyDot = 0.0f; + float currentEnemyDist = CM_MAX_TRACE_DIST; + idEntity* localEnemy = NULL; + + //find player if within certain radius + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + possibleEnemy = gameLocal.entities[i]; + if ( !possibleEnemy || (possibleEnemy->GetOrigin() - GetOrigin()).Length() > enemyRadius ) { + continue; + } + localEnemy = WhosClosest( possibleEnemy, localEnemy, currentEnemyDot, currentEnemyDist ); + } + } + if ( localEnemy ) { + return localEnemy; + } + + //otherwise look for bug triggers + float bestDist = 9999999; + int bestIndex = -1; + idBounds bounds = idBounds( GetOrigin() ).Expand( spawnArgs.GetFloat( "enemy_check_radius" )); + int numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, -1, entityList, MAX_GENTITIES ); + for ( int i=0; ispawnArgs.GetInt( "bug_trigger", "0" ) ) { + continue; + } + float dist = ( GetOrigin() - possibleEnemy->GetOrigin() ).Length(); + if ( dist < bestDist ) { + bestIndex = i; + bestDist = dist; + } + } + + if ( bestIndex >= 0 ) { + if ( entityList[bestIndex]->IsType( idActor::Type ) ) { + physicsObj.SetContents( 0 ); + } else { + physicsObj.SetContents( CONTENTS_PROJECTILE ); + } + return entityList[bestIndex]; + } + + return NULL; +} + +idVec3 hhProjectileBug::DetermineEnemyPosition( const idEntity* ent ) const { + float randomOffset = spawnArgs.GetFloat( "offset_max", "70" ) * gameLocal.random.RandomFloat(); + if ( ent && ent->IsType( idActor::Type ) ) { + const idActor *entActor = static_cast(ent); + return entActor->GetEyePosition() + idVec3(0,0,randomOffset); + } + + return ent->GetOrigin() + idVec3(0,0,randomOffset); +} + +void hhProjectileBug::Event_TrackTarget() { + idEntity *newEnemy = DetermineEnemy(); + if( !newEnemy ) { + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + return; + } + enemy = newEnemy; + + idVec3 enemyDir = DetermineEnemyDir( enemy.GetEntity() ); + idVec3 currentDir = GetAxis()[0]; + idVec3 newDir = currentDir*(1-turnFactor) + enemyDir*turnFactor; + newDir.Normalize(); + if ( driver.IsValid() ) { + driver->SetAxis(newDir.ToMat3()); + } else { + SetAxis(newDir.ToMat3()); + } + + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + + PostEventMS( &EV_Guide, updateRate ); +} + +void hhProjectileBug::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + physicsObj.SetLinearVelocity( hhProjectile::GetBounceDirection( physicsObj.GetLinearVelocity(), collision->c.normal ) ); + idThread::ReturnInt( 0 ); +} + +//================ +//hhProjectileBug::Save +//================ +void hhProjectileBug::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( enemyRadius ); +} + +//================ +//hhProjectileBug::Restore +//================ +void hhProjectileBug::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( enemyRadius ); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilebug.h b/neo/prey/Prey/prey_projectilebug.h new file mode 100644 index 00000000..d44c3d4d --- /dev/null +++ b/neo/prey/Prey/prey_projectilebug.h @@ -0,0 +1,20 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_BUG_H +#define __HH_PROJECTILE_BUG_H + +class hhProjectileBug: public hhProjectileTracking { +public: + CLASS_PROTOTYPE( hhProjectileBug ) + idEntity* DetermineEnemy(); + void Event_TrackTarget(); + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + idVec3 DetermineEnemyPosition( const idEntity* ent ) const; + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); +protected: + float enemyRadius; +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilebugtrigger.cpp b/neo/prey/Prey/prey_projectilebugtrigger.cpp new file mode 100644 index 00000000..7a06c846 --- /dev/null +++ b/neo/prey/Prey/prey_projectilebugtrigger.cpp @@ -0,0 +1,40 @@ +#include "precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileBugTrigger ) + EVENT( EV_Collision_Flesh, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Metal, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_AltMetal, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Wood, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Stone, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Glass, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Liquid, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_CardBoard, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Tile, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Forcefield, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Chaff, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Pipe, hhProjectileBugTrigger::Event_Collision_Impact ) + EVENT( EV_Collision_Wallwalk, hhProjectileBugTrigger::Event_Collision_Impact ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileBugTrigger::Event_AllowCollision_Collide ) + EVENT( EV_Touch, hhProjectileBugTrigger::Event_Touch ) +END_CLASS + +void hhProjectileBugTrigger::Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ) { + ProcessCollision(collision, velocity); + RemoveProjectile( spawnArgs.GetInt( "remove_time", "1500" ) ); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + idThread::ReturnInt( 1 ); +} + +void hhProjectileBugTrigger::Event_Touch( idEntity *other, trace_t *trace ) { + idAI *ownerAI = static_cast(owner.GetEntity()); + if ( other && ownerAI && ownerAI->GetEnemy() == other ) { + other->Damage( this, owner.GetEntity(), idVec3( 0,0,1 ), spawnArgs.GetString( "def_damage" ), 1.0, 0 ); + GetPhysics()->SetContents( 0 ); + } +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilebugtrigger.h b/neo/prey/Prey/prey_projectilebugtrigger.h new file mode 100644 index 00000000..5adadc3a --- /dev/null +++ b/neo/prey/Prey/prey_projectilebugtrigger.h @@ -0,0 +1,13 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_BUGTRIGGER_H +#define __HH_PROJECTILE_BUGTRIGGER_H + +class hhProjectileBugTrigger: public hhProjectile { +public: + CLASS_PROTOTYPE( hhProjectileBugTrigger ) + void Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilecocoon.cpp b/neo/prey/Prey/prey_projectilecocoon.cpp new file mode 100644 index 00000000..1b71a723 --- /dev/null +++ b/neo/prey/Prey/prey_projectilecocoon.cpp @@ -0,0 +1,105 @@ +#include "precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +const idEventDef EV_Guide( "" ); + +CLASS_DECLARATION( hhProjectileTracking, hhProjectileCocoon ) + EVENT( EV_Collision_Flesh, hhProjectileCocoon::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileCocoon::Event_Collision_Proj ) + EVENT( EV_Collision_AltMetal, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Liquid, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Chaff, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileCocoon::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileCocoon::Event_Collision_Bounce ) + + EVENT( EV_Guide, hhProjectileCocoon::Event_TrackTarget ) + EVENT( EV_AllowCollision_Flesh, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Metal, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_AltMetal, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Wood, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Stone, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Glass, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Liquid, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_CardBoard, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Tile, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Forcefield, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Pipe, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Wallwalk, hhProjectileCocoon::Event_AllowCollision ) + EVENT( EV_AllowCollision_Spirit, hhProjectileCocoon::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Chaff, hhProjectileCocoon::Event_AllowCollision_Collide ) + +END_CLASS + +void hhProjectileCocoon::Spawn() { + nextBounceTime = 0; +} + +void hhProjectileCocoon::Event_TrackTarget() { + idVec3 newVelocity; + if( !enemy.IsValid() ) { + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + return; + } + + idVec3 enemyDir = DetermineEnemyDir( enemy.GetEntity() ); + idVec3 currentDir = GetAxis()[0]; + idVec3 newDir = currentDir*(1-turnFactor) + enemyDir*turnFactor; + newDir.Normalize(); + if ( driver.IsValid() ) { + driver->SetAxis(newDir.ToMat3()); + } else { + SetAxis(newDir.ToMat3()); + } + newVelocity = GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ]; + newVelocity *= spawnArgs.GetFloat( "bounce", "1.0" ); + physicsObj.SetLinearVelocity( newVelocity ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); +} + +void hhProjectileCocoon::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + if ( gameLocal.time >= nextBounceTime ) { + nextBounceTime = gameLocal.time + spawnArgs.GetFloat( "bounce_freq", "200" ); + + StopSound( SND_CHANNEL_BODY, true ); + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, true, NULL ); + idEntity *ent = gameLocal.entities[ collision->c.entityNum ]; + Event_TrackTarget(); + BounceSplat( GetOrigin(), -collision->c.normal ); + } + idThread::ReturnInt( 0 ); +} + +void hhProjectileCocoon::Event_Collision_Proj( const trace_t* collision, const idVec3 &velocity ) { + idEntity *ent = gameLocal.entities[ collision->c.entityNum ]; + if ( ent && ent->IsType( idProjectile::Type ) ) { + Explode( collision, velocity, 0 ); + } else { + Event_Collision_Bounce( collision, velocity ); + } + + idThread::ReturnInt( 0 ); +} + +void hhProjectileCocoon::Event_AllowCollision( const trace_t* collision ) { + idEntity *ent = gameLocal.entities[ collision->c.entityNum ]; + if ( ent && ent->IsType( idProjectile::Type ) ) { + int foo = 0; + } + + idThread::ReturnInt( 1 ); +} + +void hhProjectileCocoon::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + hhProjectileTracking::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilecocoon.h b/neo/prey/Prey/prey_projectilecocoon.h new file mode 100644 index 00000000..25100a08 --- /dev/null +++ b/neo/prey/Prey/prey_projectilecocoon.h @@ -0,0 +1,19 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_COCOON_H +#define __HH_PROJECTILE_COCOON_H + +class hhProjectileCocoon: public hhProjectileTracking { +public: + CLASS_PROTOTYPE( hhProjectileCocoon ) + void Spawn(); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); +protected: + void Event_Collision_Proj( const trace_t* collision, const idVec3 &velocity ); + void Event_TrackTarget(); + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); + void Event_AllowCollision( const trace_t* collision ); + int nextBounceTime; +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilecrawlergrenade.cpp b/neo/prey/Prey/prey_projectilecrawlergrenade.cpp new file mode 100644 index 00000000..96a0c08f --- /dev/null +++ b/neo/prey/Prey/prey_projectilecrawlergrenade.cpp @@ -0,0 +1,557 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileCrawlerGrenade + +***********************************************************************/ +const idEventDef EV_ApplyExpandWound( "" ); + +const idEventDef EV_DyingState( "" ); +const idEventDef EV_DeadState( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileCrawlerGrenade ) + EVENT( EV_ApplyExpandWound, hhProjectileCrawlerGrenade::Event_ApplyExpandWound ) + EVENT( EV_DyingState, hhProjectileCrawlerGrenade::EnterDyingState ) + EVENT( EV_DeadState, hhProjectileCrawlerGrenade::EnterDeadState ) + + EVENT( EV_Collision_Flesh, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Metal, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileCrawlerGrenade::Event_Collision_Bounce ) + //EVENT( EV_Collision_Chaff, hhProjectileCrawlerGrenade::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileCrawlerGrenade::Event_Collision_DisturbLiquid ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileCrawlerGrenade::Event_AllowCollision_Collide ) +END_CLASS + +/* +================= +hhProjectileCrawlerGrenade::Spawn +================= +*/ +void hhProjectileCrawlerGrenade::Spawn() { + modelScale.Init( gameLocal.time, 0, 1.0f, 1.0f ); + modelProxy = NULL; + + InitCollisionInfo(); + + //doesn't matter for single player, only for network logic -rww + modelProxyCopyDone = false; + + if( !gameLocal.isClient ) { + SpawnModelProxy(); + } + + BecomeActive( TH_TICKER ); + + if (gameLocal.isClient) + { //rww - do this right away on the client + //Get rid of our model. The modelProxy is our model now. + SetModel( "" ); + } + + //rww - allow events on client + fl.clientEvents = true; +} + +/* +================= +hhProjectileCrawlerGrenade::~hhProjectileCrawlerGrenade +================= +*/ +hhProjectileCrawlerGrenade::~hhProjectileCrawlerGrenade() { + SAFE_REMOVE( modelProxy ); +} + +/* +================= +hhProjectileCrawlerGrenade::InitCollisionInfo +================= +*/ +void hhProjectileCrawlerGrenade::InitCollisionInfo() { + memset( &collisionInfo, 0, sizeof(trace_t) ); + collisionInfo.fraction = 1.0f; +} + +/* +================= +hhProjectileCrawlerGrenade::CopyToModelProxy +================= +*/ +void hhProjectileCrawlerGrenade::CopyToModelProxy() +{ + //rww - do this the right way with an inheriting entityDef + //idDict args = spawnArgs; + //args.Delete( "spawnclass" ); + //args.Delete( "name" ); + + idDict args; + idDict *setArgs; + idStr str; + + if (gameLocal.isClient) + { + setArgs = &modelProxy->spawnArgs; + } + else + { + args.Clear(); + setArgs = &args; + } + + setArgs->Set( "owner", GetName() ); + setArgs->SetVector( "origin", GetOrigin() ); + setArgs->SetMatrix( "rotation", GetAxis() ); + + //copy the model over + if (spawnArgs.GetString("model", "", str)) + { + setArgs->Set("model", str.c_str()); + if (gameLocal.isClient && modelProxy.IsValid()) + { + modelProxy->SetModel(str.c_str()); + } + } + + //these are now taken care of in the ent def. + /* + setArgs->SetBool( "useCombatModel", true ); + setArgs->SetBool( "transferDamage", false ); + setArgs->SetBool( "solid", false ); + */ + + if (!gameLocal.isClient) + { + modelProxy = gameLocal.SpawnObject("projectile_crawler_proxy", &args); + } + + if( modelProxy.IsValid() ) { + modelProxy->Bind( this, true ); + modelProxy->CycleAnim( "idle", ANIMCHANNEL_ALL ); + } + + //for debugging + //spawnArgs.CompareArgs(modelProxy->spawnArgs); + + modelProxyCopyDone = true; +} + +/* +================= +hhProjectileCrawlerGrenade::SpawnModelProxy +================= +*/ +void hhProjectileCrawlerGrenade::SpawnModelProxy() { + if (!gameLocal.isClient) + { + CopyToModelProxy(); + } + + //Get rid of our model. The modelProxy is our model now. + SetModel( "" ); +} + +/* +================= +hhProjectileCrawlerGrenade::Ticker +================= +*/ +void hhProjectileCrawlerGrenade::Ticker() { + if( state == StateDying ) { + if( modelProxy.IsValid() ) { + modelProxy->SetDeformation(DEFORMTYPE_SCALE, modelScale.GetCurrentValue(gameLocal.time)); + } + } +} + +/* +================ +hhProjectileCrawlerGrenade::Launch +================ +*/ +void hhProjectileCrawlerGrenade::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + if( modelProxy.IsValid() ) { + modelProxy->CycleAnim( "flight", ANIMCHANNEL_ALL ); + } + + float delayBeforeDying = spawnArgs.GetFloat( "delayBeforeDying" ); + float fuse = spawnArgs.GetFloat( "fuse" ); + + inflateDuration = SEC2MS( hhMath::ClampFloat(0.0f, fuse, fuse - delayBeforeDying) ); + + PostEventSec( &EV_DyingState, delayBeforeDying ); + PostEventSec( &EV_DeadState, fuse ); +} + +/* +================ +hhProjectileCrawlerGrenade::SpawnFlyFx +================ +*/ +void hhProjectileCrawlerGrenade::SpawnFlyFx() { + if( modelProxy.IsValid() ) { + modelProxy->BroadcastFxInfoAlongBonePrefix( &spawnArgs, "fx_fly", "joint_legStub" ); + } +} + +/* +================= +hhProjectileCrawlerGrenade::Hide +================= +*/ +void hhProjectileCrawlerGrenade::Hide() { + hhProjectile::Hide(); + + if( modelProxy.IsValid() ) { + modelProxy->Hide(); + } +} + +/* +================= +hhProjectileCrawlerGrenade::Show +================= +*/ +void hhProjectileCrawlerGrenade::Show() { + hhProjectile::Show(); + + if( modelProxy.IsValid() ) { + modelProxy->Show(); + } +} + +/* +================= +hhProjectileCrawlerGrenade::RemoveProjectile +================= +*/ +void hhProjectileCrawlerGrenade::RemoveProjectile( const int removeDelay ) { + hhProjectile::RemoveProjectile( removeDelay ); + + if( modelProxy.IsValid() ) { + modelProxy->PostEventMS( &EV_Remove, removeDelay ); + } +} + +/* +================= +hhProjectileCrawlerGrenade::Event_ApplyExpandWound +================= +*/ +void hhProjectileCrawlerGrenade::Event_ApplyExpandWound() { + trace_t trace; + + if( !modelProxy.IsValid() || !modelProxy->GetCombatModel() ) { + return; + } + + idBounds clipBounds( modelProxy->GetRenderEntity()->bounds ); + idVec3 traceEnd = GetOrigin(); + idVec3 traceStart = traceEnd + hhUtils::RandomPointInShell( clipBounds.Expand(1.0f).GetRadius(), clipBounds.Expand(2.0f).GetRadius() ); + idVec3 jointOrigin, localOrigin, localNormal; + idMat3 jointAxis, axisTranspose; + jointHandle_t jointHandle = INVALID_JOINT; + + CancelEvents( &EV_ApplyExpandWound ); + PostEventSec( &EV_ApplyExpandWound, spawnArgs.GetFloat("expandWoundDelay") ); + + if( !gameLocal.clip.TracePoint(trace, traceStart, traceEnd, modelProxy->GetCombatModel()->GetContents(), NULL) ) { + return; + } + + if( trace.c.entityNum != entityNumber ) {//Make sure we hit ourselves + return; + } + + modelProxy->AddDamageEffect( trace, vec3_zero, spawnArgs.GetString("def_expandDamage"), (!fl.networkSync || netSyncPhysics) ); +} + +/* +================= +hhProjectileCrawlerGrenade::Event_Collision_Bounce +================= +*/ +void hhProjectileCrawlerGrenade::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + static const float minCollisionVelocity = 20.0f; + static const float maxCollisionVelocity = 90.0f; + + StopSound( SND_CHANNEL_BODY, true ); + + // Velocity in normal direction + float len = velocity * -collision->c.normal; + + if( collision->fraction < VECTOR_EPSILON || len < minCollisionVelocity ) { + idThread::ReturnInt( 0 ); + return; + } + + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, true, NULL ); + float volume = hhUtils::CalculateSoundVolume( len, minCollisionVelocity, maxCollisionVelocity ); + HH_SetSoundVolume( volume, SND_CHANNEL_BODY ); + + BounceSplat( GetOrigin(), -collision->c.normal ); + + SIMDProcessor->Memcpy( &collisionInfo, collision, sizeof(trace_t) ); + collisionInfo.fraction = 0.0f;//Sometimes fraction == 1.0f + + physicsObj.SetAngularVelocity( 0.5f*physicsObj.GetAngularVelocity() ); + + idThread::ReturnInt( 0 ); +} + +/* +================= +hhProjectileCrawlerGrenade::Event_Collision_DisturbLiquid +================= +*/ +void hhProjectileCrawlerGrenade::Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3 &velocity ) { + CancelActivates(); + EnterDeadState(); + + hhProjectile::Event_Collision_DisturbLiquid( collision, velocity ); +} + +//================ +//hhProjectileCrawlerGrenade::Save +//================ +void hhProjectileCrawlerGrenade::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( modelScale.GetStartTime() ); // idInterpolate + savefile->WriteFloat( modelScale.GetDuration() ); + savefile->WriteFloat( modelScale.GetStartValue() ); + savefile->WriteFloat( modelScale.GetEndValue() ); + savefile->WriteInt( inflateDuration ); + savefile->WriteInt( state ); + savefile->WriteTrace( collisionInfo ); + modelProxy.Save( savefile ); +} + +//================ +//hhProjectileCrawlerGrenade::Restore +//================ +void hhProjectileCrawlerGrenade::Restore( idRestoreGame *savefile ) { + float set; + savefile->ReadFloat( set ); // idInterpolate + modelScale.SetStartTime( set ); + savefile->ReadFloat( set ); + modelScale.SetDuration( set ); + savefile->ReadFloat( set ); + modelScale.SetStartValue( set ); + savefile->ReadFloat( set ); + modelScale.SetEndValue( set ); + + savefile->ReadInt( inflateDuration ); + savefile->ReadInt( reinterpret_cast ( state ) ); + + savefile->ReadTrace( collisionInfo ); + modelProxy.Restore( savefile ); +} + +/* +================= +hhProjectileCrawlerGrenade::WriteToSnapshot +================= +*/ +void hhProjectileCrawlerGrenade::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + msg.WriteBits(modelProxyCopyDone, 1); + msg.WriteBits(modelProxy.GetSpawnId(), 32); + + hhProjectile::WriteToSnapshot(msg); +} + +/* +================= +hhProjectileCrawlerGrenade::ReadFromSnapshot +================= +*/ +void hhProjectileCrawlerGrenade::ReadFromSnapshot( const idBitMsgDelta &msg ) +{ + bool newModelProxyCopyDone = !!msg.ReadBits(1); + if (modelProxy.SetSpawnId(msg.ReadBits(32))) + { + if (modelProxyCopyDone != newModelProxyCopyDone && + modelProxy.IsValid() && + modelProxy->IsType(hhGenericAnimatedPart::Type)) + { + modelProxyCopyDone = newModelProxyCopyDone; + CopyToModelProxy(); + } + } + + hhProjectile::ReadFromSnapshot(msg); +} + +/* +================= +hhProjectileCrawlerGrenade::EnterDyingState +================= +*/ +void hhProjectileCrawlerGrenade::EnterDyingState() { + state = StateDying; + modelScale.Init( gameLocal.GetTime(), inflateDuration, modelScale.GetCurrentValue(gameLocal.GetTime()), spawnArgs.GetFloat("inflateScale") ); + + StartSound( "snd_expand_screech", SND_CHANNEL_VOICE, 0, true, NULL ); + ProcessEvent( &EV_ApplyExpandWound ); +} + +/* +================= +hhProjectileCrawlerGrenade::EnterDeadState +================= +*/ +void hhProjectileCrawlerGrenade::EnterDeadState() { + state = StateDead; + CancelEvents( &EV_ApplyExpandWound ); + StopSound( SND_CHANNEL_VOICE, true ); +} + +/* +================= +hhProjectileCrawlerGrenade::CancelActivates +================= +*/ +void hhProjectileCrawlerGrenade::CancelActivates() { + CancelEvents( &EV_DyingState ); + CancelEvents( &EV_DeadState ); +} + +/*********************************************************************** + + hhProjectileStickyCrawlerGrenade + +***********************************************************************/ + +CLASS_DECLARATION( hhProjectileCrawlerGrenade, hhProjectileStickyCrawlerGrenade ) + EVENT( EV_Collision_Flesh, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Metal, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_AltMetal, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Wood, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Stone, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Glass, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_CardBoard, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Tile, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Forcefield, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Pipe, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + EVENT( EV_Collision_Wallwalk, hhProjectileStickyCrawlerGrenade::Event_Collision_Stick ) + + EVENT( EV_Activate, hhProjectileStickyCrawlerGrenade::Event_Activate ) +END_CLASS + +int hhProjectileStickyCrawlerGrenade::ProcessCollision( const trace_t* collision, const idVec3& velocity ) { + idEntity* entHit = gameLocal.entities[ collision->c.entityNum ]; + + //SAFE_REMOVE( fxFly ); + FreeLightDef(); + CancelEvents( &EV_Fizzle ); + + //physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + surfTypes_t matterType = gameLocal.GetMatterType( entHit, collision->c.material, "hhProjectile::ProcessCollision" ); + return PlayImpactSound( gameLocal.FindEntityDefDict(spawnArgs.GetString("def_damage")), collision->endpos, matterType ); +} + +idMat3 hhProjectileStickyCrawlerGrenade::DetermineCollisionAxis( const idMat3& collisionAxis ) { + return collisionAxis; +} + +void hhProjectileStickyCrawlerGrenade::BindToCollisionObject( const trace_t* collision ) { + if( !collision || collision->fraction > 1.0f ) { + return; + } + + //HUMANHEAD PCF rww 05/18/06 - wait until we receive bind info from the server + if (gameLocal.isClient) { + return; + } + //HUMANHEAD END + + idEntity* pEntity = gameLocal.entities[collision->c.entityNum]; + HH_ASSERT( pEntity ); + + // HUMANHEAD PCF pdm 05-20-06: Check for some degenerate cases to combat the server hangs happening + if (pEntity == this || this->IsBound()) { + assert(0); // Report any of these + return; + } + // HUMANHEAD END + + jointHandle_t jointHandle = CLIPMODEL_ID_TO_JOINT_HANDLE( collision->c.id ); + if ( jointHandle != INVALID_JOINT ) { + SetOrigin( collision->endpos ); + SetAxis( DetermineCollisionAxis( (-collision->c.normal).ToMat3()) ); + BindToJoint( pEntity, jointHandle, true ); + } else { + SetOrigin( collision->endpos ); + SetAxis( DetermineCollisionAxis( (-collision->c.normal).ToMat3()) ); + Bind( pEntity, true ); + } +} + +void hhProjectileStickyCrawlerGrenade::Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ) { + if (proximityDetonateTrigger.GetEntity()) { //rww - don't allow this to be called more than once in a crawler grenade's lifetime + return; + } + ProcessCollision( collision, velocity ); + + BindToCollisionObject( collision ); + + fl.ignoreGravityZones = true; + SetGravity( idVec3(0.f, 0.f, 0.f) ); + spawnArgs.SetVector("gravity", idVec3(0.f, 0.f, 0.f) ); + + BounceSplat( GetOrigin(), -collision->c.normal ); + + idDict dict; + + dict.SetVector( "origin", GetOrigin() ); + //dict.SetMatrix( "rotation", GetAxis() ); + dict.Set( "target", name.c_str() ); + + dict.SetVector( "mins", spawnArgs.GetVector("detonationMins", "-10 -10 -10") ); + dict.SetVector( "maxs", spawnArgs.GetVector("detonationMaxs", "10 10 10") ); + if (!gameLocal.isClient) { + proximityDetonateTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &dict ); + proximityDetonateTrigger->Bind( this, true ); + if ( proximityDetonateTrigger->IsType( hhTrigger::Type ) ) { + hhTrigger *trigger = static_cast(proximityDetonateTrigger.GetEntity()); + if ( trigger && trigger->IsEncroached() ) { + proximityDetonateTrigger->PostEventMS( &EV_Activate, 0, this ); + } + } + } + + if( modelProxy.IsValid() ) { + modelProxy->CycleAnim( "idle", ANIMCHANNEL_ALL ); + } + + // CJR: Added this from the normal crawler collision bounce code + SIMDProcessor->Memcpy( &collisionInfo, collision, sizeof(trace_t) ); + collisionInfo.fraction = 0.0f;//Sometimes fraction == 1.0f + + idThread::ReturnInt( 1 ); +} + +void hhProjectileStickyCrawlerGrenade::Event_Activate( idEntity *pActivator ) { + StartSound( "snd_expand_screech", SND_CHANNEL_VOICE, 0, true, NULL ); + PostEventSec( &EV_Explode, spawnArgs.GetFloat( "explodeDelay", "1.0" ) ); +} + +void hhProjectileStickyCrawlerGrenade::Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ) { + SAFE_REMOVE( fxFly ); + hhProjectile::Explode( &collisionInfo, velocity, removeDelay ); +} + diff --git a/neo/prey/Prey/prey_projectilecrawlergrenade.h b/neo/prey/Prey/prey_projectilecrawlergrenade.h new file mode 100644 index 00000000..9cd968fb --- /dev/null +++ b/neo/prey/Prey/prey_projectilecrawlergrenade.h @@ -0,0 +1,86 @@ +#ifndef __HH_PROJECTILE_CRAWLER_GRENADE_H +#define __HH_PROJECTILE_CRAWLER_GRENADE_H + +extern const idEventDef EV_ApplyExpandWound; + +/*********************************************************************** + + hhProjectileCrawlerGrenade + +***********************************************************************/ +class hhProjectileCrawlerGrenade : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileCrawlerGrenade ); + + public: + void Spawn(); + virtual ~hhProjectileCrawlerGrenade(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void Hide(); + virtual void Show(); + virtual void RemoveProjectile( const int removeDelay ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + protected: + virtual void Ticker(); + + virtual void SpawnFlyFx(); + virtual void CopyToModelProxy(); + virtual void SpawnModelProxy(); + virtual void InitCollisionInfo(); + + protected: + void Event_ApplyExpandWound(); + + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); + void Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3 &velocity ); + void EnterDyingState(); + void EnterDeadState(); + void CancelActivates(); + + protected: + enum States { + StateAlive = 0, + StateDying, + StateDead + } state; + + idInterpolate modelScale; + int inflateDuration; + + trace_t collisionInfo; + + idEntityPtr modelProxy; + + bool modelProxyCopyDone; +}; + +/*********************************************************************** + + hhProjectileStickyCrawlerGrenade + +***********************************************************************/ +class hhProjectileStickyCrawlerGrenade : public hhProjectileCrawlerGrenade { + CLASS_PROTOTYPE( hhProjectileStickyCrawlerGrenade ); + + public: + int ProcessCollision( const trace_t* collision, const idVec3& velocity ); + idMat3 DetermineCollisionAxis( const idMat3& collisionAxis ); + void Event_Activate( idEntity *pActivator ); + + protected: + virtual void BindToCollisionObject( const trace_t* collision ); + void Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ); + virtual void Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ); + + idEntityPtr proximityDetonateTrigger; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilefreezer.cpp b/neo/prey/Prey/prey_projectilefreezer.cpp new file mode 100644 index 00000000..7c8266ae --- /dev/null +++ b/neo/prey/Prey/prey_projectilefreezer.cpp @@ -0,0 +1,115 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileFreezer + +***********************************************************************/ +CLASS_DECLARATION( hhProjectile, hhProjectileFreezer ) + //EVENT( EV_Touch, hhProjectileFreezer::Event_Touch ) + + EVENT( EV_Collision_Flesh, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Metal, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_AltMetal, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Wood, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Stone, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Glass, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_CardBoard, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Tile, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Forcefield, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Pipe, hhProjectileFreezer::Event_Collision_Bounce ) + EVENT( EV_Collision_Wallwalk, hhProjectileFreezer::Event_Collision_Bounce ) +END_CLASS + +void hhProjectileFreezer::Spawn() { + decelStart = SEC2MS( spawnArgs.GetFloat("decelStart") ) + gameLocal.GetTime(); + decelEnd = SEC2MS( spawnArgs.GetFloat("decelDuration") ) + decelStart; + + collided=false; + + BecomeActive( TH_TICKER ); +} + +void hhProjectileFreezer::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + cachedVelocity = GetPhysics()->GetLinearVelocity(); + + //fl.takedamage = false; + //physicsObj.DisableImpact(); +} + +void hhProjectileFreezer::Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ) { + idEntity *entityHit = gameLocal.entities[ collision->c.entityNum ]; + if ( entityHit->IsType(idAI::Type) || entityHit->IsType(idAFEntity_Base::Type) ) { + Event_Collision_Explode(collision, velocity); + return; + } + + ProcessCollision(collision, velocity); + collided=true; + idThread::ReturnInt( 1 ); +} + +bool hhProjectileFreezer::Collide( const trace_t& collision, const idVec3& velocity ) { + if(!collided) + return hhProjectile::Collide( collision, velocity ); + else + return false; +} + + +int hhProjectileFreezer::ProcessCollision( const trace_t* collision, const idVec3& velocity ) { + idEntity* entHit = gameLocal.entities[ collision->c.entityNum ]; + + SetOrigin( collision->endpos ); + SetAxis( collision->endAxis ); + + if (entHit) { //rww - may be null on client. + DamageEntityHit( collision, velocity, entHit ); + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + surfTypes_t matterType = gameLocal.GetMatterType( entHit, collision->c.material, "hhProjectile::ProcessCollision" ); + return PlayImpactSound( gameLocal.FindEntityDefDict(spawnArgs.GetString("def_damage")), collision->endpos, matterType ); +} + +void hhProjectileFreezer::Ticker() { + float scale = 0.0f; + if( gameLocal.GetTime() > decelStart && gameLocal.GetTime() < decelEnd ) { + scale = hhMath::Sin( DEG2RAD(hhMath::MidPointLerp( 0.0f, 30.0f, 90.0f, 1.0f - hhUtils::CalculateScale(gameLocal.GetTime(), decelStart, decelEnd))) ); + + GetPhysics()->SetLinearVelocity( cachedVelocity * hhMath::ClampFloat(0.05f, 1.0f, scale) ); + } +} + +void hhProjectileFreezer::Event_Touch( idEntity *other, trace_t *trace ) { + //Supposed to be empty + +} + +void hhProjectileFreezer::Save( idSaveGame *savefile ) const { + savefile->WriteInt( decelStart ); + savefile->WriteInt( decelEnd ); + savefile->WriteVec3( cachedVelocity ); + savefile->WriteBool( collided ); +} + +void hhProjectileFreezer::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( decelStart ); + savefile->ReadInt( decelEnd ); + savefile->ReadVec3( cachedVelocity ); + savefile->ReadBool( collided ); +} + +void hhProjectileFreezer::ApplyDamageEffect( idEntity* hitEnt, const trace_t* collision, const idVec3& velocity, const char* damageDefName ) { + if( hitEnt && gameLocal.random.RandomFloat() > 0.6f ) { + hitEnt->AddDamageEffect( *collision, velocity, damageDefName, (!fl.networkSync || netSyncPhysics) ); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilefreezer.h b/neo/prey/Prey/prey_projectilefreezer.h new file mode 100644 index 00000000..3664793b --- /dev/null +++ b/neo/prey/Prey/prey_projectilefreezer.h @@ -0,0 +1,35 @@ +#ifndef __HH_PROJECTILE_FREEZER_H +#define __HH_PROJECTILE_FREEZER_H + +class hhProjectileFreezer : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileFreezer ); + + public: + void Spawn(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Collide( const trace_t& collision, const idVec3& velocity ); + virtual int ProcessCollision( const trace_t* collision, const idVec3& velocity ); + + protected: + virtual void Ticker(); + + protected: + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Collision_Bounce( const trace_t* collision, const idVec3 &velocity ); + + void ApplyDamageEffect( idEntity* hitEnt, const trace_t* collision, const idVec3& velocity, const char* damageDefName ); + + protected: + int decelStart; + int decelEnd; + + idVec3 cachedVelocity; + bool collided; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilegasbagpod.cpp b/neo/prey/Prey/prey_projectilegasbagpod.cpp new file mode 100644 index 00000000..dc6152f6 --- /dev/null +++ b/neo/prey/Prey/prey_projectilegasbagpod.cpp @@ -0,0 +1,89 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION(hhProjectile, hhProjectileGasbagPod) + EVENT(EV_Collision_Flesh, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Metal, hhProjectileGasbagPod::Event_Collision_Proj) + EVENT(EV_Collision_AltMetal, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Wood, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Stone, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Glass, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Liquid, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_CardBoard, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Tile, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Forcefield, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Chaff, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Wallwalk, hhProjectileGasbagPod::Event_Collision_SpawnPod) + EVENT(EV_Collision_Pipe, hhProjectileGasbagPod::Event_Collision_SpawnPod) + + EVENT(EV_AllowCollision_Flesh, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Metal, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_AltMetal, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Wood, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Stone, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Glass, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Liquid, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_CardBoard, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Tile, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Forcefield, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Pipe, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Wallwalk, hhProjectileGasbagPod::Event_AllowCollision_Collide) + EVENT(EV_AllowCollision_Spirit, hhProjectileGasbagPod::Event_AllowCollision_PassThru) + EVENT(EV_AllowCollision_Chaff, hhProjectileGasbagPod::Event_AllowCollision_Collide) //bjk: shield blocks all +END_CLASS + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +void hhProjectileGasbagPod::Spawn(void) { + BecomeActive(TH_TICKER); +} + +void hhProjectileGasbagPod::Ticker(void) { + renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM2] += .1f; + if (renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM2] > 1.0f) { + renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM2] = 1.0f; + BecomeInactive(TH_TICKER); + } +} + +void hhProjectileGasbagPod::Event_Collision_SpawnPod(const trace_t* collision, const idVec3 &velocity) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 avel = GetPhysics()->GetAngularVelocity(); + + physicsObj.PutToRest(); + physicsObj.SetContents(0); + PostEventMS(&EV_Remove, 0); + + idDict args; + args.Clear(); + args.Set("origin", (GetPhysics()->GetOrigin()).ToString()); + args.Set("axis", (GetPhysics()->GetAxis()).ToString()); + args.Set("nodrop", "1"); + + idEntity *ent = gameLocal.SpawnObject("object_pod", &args); + + if (ent) { + ent->GetPhysics()->SetLinearVelocity(vel); + ent->GetPhysics()->SetAngularVelocity(avel); + + if (owner.IsValid()) { + owner->PostEventMS(&EV_NewPod, 0, ent); + } + } + + idThread::ReturnInt(1); +} + +void hhProjectileGasbagPod::Event_Collision_Proj(const trace_t* collision, const idVec3 &velocity) { + idEntity *ent = gameLocal.entities[collision->c.entityNum]; + if (ent && ent->IsType(idProjectile::Type)) { + Explode(collision, velocity, 0); + } else { + Event_Collision_SpawnPod(collision, velocity); + } + + idThread::ReturnInt(0); +} + +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilegasbagpod.h b/neo/prey/Prey/prey_projectilegasbagpod.h new file mode 100644 index 00000000..f496e28c --- /dev/null +++ b/neo/prey/Prey/prey_projectilegasbagpod.h @@ -0,0 +1,20 @@ +#ifndef __HH_PROJECTILE_GASBAGPOD_H +#define __HH_PROJECTILE_GASBAGPOD_H + +class hhProjectileGasbagPod : public hhProjectile { + CLASS_PROTOTYPE(hhProjectileGasbagPod); +#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build + void Event_Collision_SpawnPod(const trace_t* collision, const idVec3 &velocity) {}; + void Event_Collision_Proj(const trace_t* collision, const idVec3 &velocity) {}; +#else +public: + void Spawn(void); + virtual void Ticker(void); + +protected: + void Event_Collision_SpawnPod(const trace_t* collision, const idVec3 &velocity); + void Event_Collision_Proj(const trace_t* collision, const idVec3 &velocity); +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +}; + +#endif diff --git a/neo/prey/Prey/prey_projectilehiderweapon.cpp b/neo/prey/Prey/prey_projectilehiderweapon.cpp new file mode 100644 index 00000000..bab793c8 --- /dev/null +++ b/neo/prey/Prey/prey_projectilehiderweapon.cpp @@ -0,0 +1,136 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileHider + +***********************************************************************/ +CLASS_DECLARATION( hhProjectile, hhProjectileHider ) +END_CLASS + +void hhProjectileHider::ApplyDamageEffect( idEntity* hitEnt, const trace_t& collision, const idVec3& velocity, const char* damageDefName ) { + //This is used to allow the hider weapon shots to streak when colliding on the ground + if( hitEnt ) { + hitEnt->AddDamageEffect( collision, velocity.ToNormal(), damageDefName, (!fl.networkSync || netSyncPhysics) ); + } +} + +/*********************************************************************** + + hhProjectileHiderCanister + +***********************************************************************/ +const idEventDef EV_CollidedWithChaff( "", "tv", 'd' ); +CLASS_DECLARATION( hhProjectileHider, hhProjectileHiderCanister ) + EVENT( EV_Collision_Flesh, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileHiderCanister::Event_Collision_Explode ) + EVENT( EV_CollidedWithChaff, hhProjectileHiderCanister::Event_Collision_ExplodeChaff ) +END_CLASS + +void hhProjectileHiderCanister::Spawn() { + numSubProjectiles = spawnArgs.GetInt( "numSubProjectiles" ); + const idDict *dict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_subProjectile"), false ); + if (!dict) { + gameLocal.Error( "No def_subProjectile defined for entity '%s'.\n", GetName() ); + } + subProjectileDict = *dict; + subSpread = DEG2RAD( spawnArgs.GetFloat("spread") ); + subBounce = spawnArgs.GetFloat( "subBounce", "1" ); + bScatter = spawnArgs.GetBool( "subScatter", "0" ); +} + +void hhProjectileHiderCanister::SpawnRicochetSpray( const idVec3& bounceVector ) { + hhProjectile* projectile = NULL; + idVec3 dir; + idMat3 projAxis; + idAngles projAngle; + idMat3 bounceAxis; + + if( !bScatter ) { + bounceAxis = bounceVector.ToNormal().ToMat3(); + subProjectileDict.SetVector( "velocity", idVec3(bounceVector.Length() * subBounce, 0.0f, 0.0f) ); + } + + for( int iIndex = 0; iIndex < numSubProjectiles; ++iIndex ) { + if( bScatter ) { + bounceAxis = hhUtils::RandomVector().ToNormal().ToMat3(); + subProjectileDict.SetVector( "velocity", idVec3(bounceVector.Length() * subBounce, 0.0f, 0.0f) ); + } + dir = hhUtils::RandomSpreadDir( bounceAxis, subSpread ); + projAngle = dir.ToAngles(); + projAngle[2] = GetAxis().ToAngles()[2]; + projAxis = projAngle.ToMat3(); + + //HUMANHEAD rww - now local + //projectile = hhProjectile::SpawnProjectile( &subProjectileDict ); + projectile = hhProjectile::SpawnClientProjectile( &subProjectileDict ); + projectile->spawnArgs.Set( "weapontype", spawnArgs.GetString("weapontype", "NONE1") ); + projectile->Create( owner.GetEntity(), GetOrigin(), bounceAxis ); + projectile->Launch( GetOrigin(), projAxis, vec3_zero ); + projectile->SetParentProjectile( this ); + } +} + +void hhProjectileHiderCanister::SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ) { + //Spawn debris along ground with respect to our current direction + idVec3 dir = collisionDir; + dir.ProjectOntoPlane( -GetPhysics()->GetGravityNormal() ); + dir.Normalize(); + hhProjectileHider::SpawnDebris( dir, collisionDir ); +} + +void hhProjectileHiderCanister::Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ) { + hhProjectile::Event_Collision_Explode( collision, velocity ); + + idEntity *entityHit = gameLocal.entities[ collision->c.entityNum ]; + if (entityHit->IsType(idAI::Type) || entityHit->IsType(idPlayer::Type)) + numSubProjectiles = 0; + + SetOrigin(collision->endpos+collision->c.normal*8.0f); + + //rww - these are purely local now, because they cause bandwidth murder. + SpawnRicochetSpray( hhProjectile::GetBounceDirection(velocity, collision->c.normal, this, NULL) ); + + idThread::ReturnInt( 1 ); +} + +void hhProjectileHiderCanister::Event_Collision_ExplodeChaff( const trace_t* collision, const idVec3& velocity ) { + fl.takedamage = false; + hhProjectile::Event_Collision_Explode( collision, velocity ); + idThread::ReturnInt( 1 ); +} + + +void hhProjectileHiderCanister::Save( idSaveGame *savefile ) const { + savefile->WriteInt( numSubProjectiles ); + savefile->WriteDict( &subProjectileDict ); + savefile->WriteFloat( subSpread ); + savefile->WriteBool( bScatter ); +} + +void hhProjectileHiderCanister::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( numSubProjectiles ); + savefile->ReadDict( &subProjectileDict ); + savefile->ReadFloat( subSpread ); + savefile->ReadBool( bScatter ); + + subBounce = spawnArgs.GetFloat( "subBounce", "1" ); +} + +void hhProjectileHiderCanister::Killed( idEntity *inflicter, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + hhProjectileHider::Killed( inflicter, attacker, damage, dir, location ); + //bScatter = true; + SpawnRicochetSpray( dir * (damage * 10.0f) ); +} diff --git a/neo/prey/Prey/prey_projectilehiderweapon.h b/neo/prey/Prey/prey_projectilehiderweapon.h new file mode 100644 index 00000000..227a3f8f --- /dev/null +++ b/neo/prey/Prey/prey_projectilehiderweapon.h @@ -0,0 +1,45 @@ +#ifndef __HH_PROJECTILE_HIDER_WEAPON_H +#define __HH_PROJECTILE_HIDER_WEAPON_H + +/*********************************************************************** + + hhProjectileHider + +***********************************************************************/ +class hhProjectileHider : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileHider ); + + protected: + virtual void ApplyDamageEffect( idEntity* entHit, const trace_t& collision, const idVec3& velocity, const char* damageDefName ); +}; + +/*********************************************************************** + + hhProjectileHiderCanister + +***********************************************************************/ +class hhProjectileHiderCanister : public hhProjectileHider { + CLASS_PROTOTYPE( hhProjectileHiderCanister ); + + public: + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual void SpawnRicochetSpray( const idVec3& bounceVelocity ); + virtual void SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ); + virtual void Killed( idEntity *inflicter, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ); + void Event_Collision_ExplodeChaff( const trace_t* collision, const idVec3& velocity ); + + protected: + int numSubProjectiles; + idDict subProjectileDict; + float subSpread; + float subBounce; + bool bScatter; +}; + +#endif diff --git a/neo/prey/Prey/prey_projectilemine.cpp b/neo/prey/Prey/prey_projectilemine.cpp new file mode 100644 index 00000000..86623dbb --- /dev/null +++ b/neo/prey/Prey/prey_projectilemine.cpp @@ -0,0 +1,505 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhHarvesterMine + +***********************************************************************/ +const idEventDef EV_ApplyAttractionTowards( "applyAttractionTowards", "e" ); + +CLASS_DECLARATION( hhRenderEntity, hhHarvesterMine ) + EVENT( EV_Activate, hhHarvesterMine::Event_Detonate ) + EVENT( EV_ApplyAttractionTowards, hhHarvesterMine::Event_ApplyAttractionTowards ) + EVENT( EV_Explode, hhHarvesterMine::Event_Explode ) +END_CLASS + +/* +================ +hhHarvesterMine::hhHarvesterMine +================ +*/ +hhHarvesterMine::hhHarvesterMine() { + proximityDetonateTrigger = NULL; + proximityAttractionTrigger = NULL; +} + +/* +================ +hhHarvesterMine::~hhHarvesterMine +================ +*/ +hhHarvesterMine::~hhHarvesterMine() { + SAFE_REMOVE( proximityDetonateTrigger ); + SAFE_REMOVE( proximityAttractionTrigger ); +} + +/* +================ +hhHarvesterMine::Spawn +================ +*/ +void hhHarvesterMine::Spawn() { + SpawnTriggers(); +} + +/* +================ +hhHarvesterMine::InitPhysics +================ +*/ +void hhHarvesterMine::InitPhysics( const idVec3& start, const idMat3& axis, const idVec3& pushVelocity ) { + float speed; + float linear_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + idVec3 gravVec; + + speed = spawnArgs.GetVector( "velocity", "0 0 0" ).Length(); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + + if ( mass <= 0 ) { + gameLocal.Error( "Invalid mass on '%s'\n", GetClassname() ); + } + + gravVec = gameLocal.GetGravity(); + gravVec.NormalizeFast(); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.GetClipModel()->SetOwner( DetermineClipModelOwner() ); + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, 1.0f, contact_friction ); + + physicsObj.SetBouncyness( bounce ); + physicsObj.SetGravity( gravVec * gravity ); + physicsObj.SetContents( DetermineContents() ); + + physicsObj.SetClipMask( MASK_SHOT_RENDERMODEL ); + physicsObj.SetLinearVelocity( axis[ 0 ] * speed + pushVelocity ); + + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + SetPhysics( &physicsObj ); +} + +/* +================ +hhHarvesterMine::SpawnTriggers +================ +*/ +void hhHarvesterMine::SpawnTriggers() { + idDict dict; + + dict.SetVector( "origin", GetOrigin() ); + dict.SetMatrix( "rotation", GetAxis() ); + dict.Set( "target", name.c_str() ); + dict.SetInt( "triggerBehavior", TB_FRIENDLIES_ONLY ); + + dict.SetVector( "mins", spawnArgs.GetVector("detonationMins", "-10 -10 -10") ); + dict.SetVector( "maxs", spawnArgs.GetVector("detonationMaxs", "10 10 10") ); + proximityDetonateTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_detonateTrigger"), &dict ); + proximityDetonateTrigger->Bind( this, true ); + + dict.SetVector( "mins", spawnArgs.GetVector("attractionMins", "-20 -20 -20") ); + dict.SetVector( "maxs", spawnArgs.GetVector("attractionMaxs", "20 20 20") ); + dict.SetFloat( "refire", spawnArgs.GetFloat("attractionUpdateFrequency") ); + dict.Set( "eventDef", "applyAttractionTowards" ); + proximityAttractionTrigger = gameLocal.SpawnObject( spawnArgs.GetString("def_attractionTrigger"), &dict ); + proximityAttractionTrigger->Bind( this, true ); +} + +/* +================ +hhHarvesterMine::Create +================ +*/ +void hhHarvesterMine::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) { + idStr shaderName; + idVec3 light_color; + idVec3 light_offset; + + Unbind(); + + SetOrigin( start ); + SetAxis( axis ); + + this->owner = owner; + + SIMDProcessor->Memset( &renderLight, 0, sizeof( renderLight ) ); + shaderName = spawnArgs.GetString( "mtr_light_shader" ); + if ( *shaderName ) { + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius = spawnArgs.GetVector( "light_size" ); + 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; + } + + spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); + + GetPhysics()->SetContents( 0 ); + + state = CREATED; +} + +/* +================= +hhHarvesterMine::Launch +================= +*/ +void hhHarvesterMine::Launch( const idVec3 &start, const idMat3& axis, const idVec3 &pushVelocity ) { + int anim = 0; + + if ( health ) { + fl.takedamage = true; + } + + Unbind(); + + //HUMANHEAD: aob - moved logic to helper function + InitPhysics( start, axis, pushVelocity ); + //HUMANHEAD END + + if ( !gameLocal.isClient ) { + PostEventSec( &EV_Explode, hhMath::hhMax(0.0f, spawnArgs.GetFloat("fuse")) ); + } + + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, true, NULL ); + + state = LAUNCHED; +} + +/* +================ +hhHarvesterMine::Killed +================ +*/ +void hhHarvesterMine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + //HUMANHEAD: aob - added collision so we can get explosion to show up when killed + trace_t collision; + + collision.fraction = 0.0f; + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.entityNum = attacker->entityNumber; + collision.c.normal = inflictor->GetOrigin() - GetOrigin(); + collision.c.normal.Normalize(); + Explode( &collision ); +} + +/* +================ +hhHarvesterMine::Collide +================ +*/ +bool hhHarvesterMine::Collide( const trace_t& collision, const idVec3& velocity ) { + return false;//Never stop because of collision, just bounce +} + +/* +================ +hhHarvesterMine::DetermineContents +================ +*/ +int hhHarvesterMine::DetermineContents() { + return CONTENTS_SOLID; +} + +/* +================ +hhHarvesterMine::DetermineClipModelOwner +================ +*/ +idEntity* hhHarvesterMine::DetermineClipModelOwner() { + return (spawnArgs.GetBool("collideWithOwner")) ? this : owner.GetEntity(); +} + +/* +================= +hhHarvesterMine::RemoveProjectile +================= +*/ +void hhHarvesterMine::RemoveProjectile( const int removeDelay ) { + Hide(); + RemoveBinds();//Remove any fx we have because they aren't hidden + PostEventMS( &EV_Remove, removeDelay ); +} + +/* +================ +hhHarvesterMine::Explode +================ +*/ +void hhHarvesterMine::Explode( const trace_t *collision ) { + const char *light_shader; + float light_fadetime; + int removeDelay; + trace_t collisionInfo; + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + //HUMANHEAD: aob + if( collision && collision->fraction < 1.0f ) { + memcpy( &collisionInfo, collision, sizeof(trace_t) ); + } else { + collisionInfo.fraction = 0.0f; + collisionInfo.endpos = GetOrigin(); + collisionInfo.endAxis = GetAxis(); + collisionInfo.c.entityNum = gameLocal.world->entityNumber; + collisionInfo.c.normal = idVec3(0.0f,0.0f,1.0f); + } + + removeDelay = spawnArgs.GetFloat( "remove_time", "200" ); + //HUMANHEAD END + + // play sound + //HUMANHEAD: aob - in case the sound length is longer than removeTime + int length = 0; + StartSound( "snd_explode", SND_CHANNEL_BODY, 0, true, &length ); + removeDelay = hhMath::hhMax( length, removeDelay ); + //HUMANHEAD END + + FreeLightDef(); + + // explosion light + light_shader = spawnArgs.GetString( "mtr_explode_light_shader" ); + if ( *light_shader ) { + renderLight.shader = declManager->FindMaterial( light_shader, false ); + renderLight.pointLight = true; + renderLight.lightRadius = spawnArgs.GetVector( "explode_light_size" ); + spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor ); + renderLight.shaderParms[SHADERPARM_RED] = lightColor[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = lightColor[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = lightColor[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime" ); + lightStartTime = gameLocal.time; + lightEndTime = gameLocal.time + SEC2MS( light_fadetime ); + BecomeActive( TH_THINK ); + } + + if( !gameLocal.isClient ) { + SpawnCollisionFX( &collisionInfo, "fx_detonate" ); + SpawnDebris( collisionInfo.c.normal, physicsObj.GetLinearVelocity().ToMat3()[0] ); + } + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + state = EXPLODED; + + if ( gameLocal.isClient ) { + return; + } + + // splash damage + idStr splash_damage = spawnArgs.GetString( "def_splash_damage" ); + if ( splash_damage.Length() ) { + gameLocal.RadiusDamage( collisionInfo.endpos, this, owner.GetEntity(), this, this, splash_damage ); + } + + //HUMANHEAD: aob - moved logic to helper function + RemoveProjectile( removeDelay ); + //HUMANHEAD END +} + +/* +================ +hhHarvesterMine::SpawnCollisionFX +================ +*/ +void hhHarvesterMine::SpawnCollisionFX( const trace_t* collision, const char* fxKey ) { + hhFxInfo fxInfo; + + if( !collision || collision->fraction >= 1.0f ) { + return; + } + + fxInfo.SetNormal( collision->c.normal ); + fxInfo.RemoveWhenDone( true ); + + BroadcastFxInfoPrefixedRandom( fxKey, GetOrigin(), GetAxis(), &fxInfo ); +} + +/* +================ +hhHarvesterMine::SpawnDebris +================ +*/ +void hhHarvesterMine::SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ) { + int fxdebris = spawnArgs.GetInt( "debris_count" ); + if( !fxdebris ) { + return; + } + + idDebris *debris = NULL; + idEntity *ent = NULL; + int amount = 0; + const idDict *dict = NULL; + for( const idKeyValue* kv = spawnArgs.MatchPrefix("def_debris", NULL); kv; kv = spawnArgs.MatchPrefix("def_debris", kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + dict = gameLocal.FindEntityDefDict( kv->GetValue().c_str(), false ); + if( !dict ) { + continue; + } + + amount = gameLocal.random.RandomInt( fxdebris ); + for ( int i = 0; i < amount; i++ ) { + //HUMANHEAD: aob + idVec3 dir = hhUtils::RandomSpreadDir( collisionNormal.ToMat3(), DEG2RAD(spawnArgs.GetFloat("spread_debris", "10")) ); + //HUMAMHEAD END + + gameLocal.SpawnEntityDef( *dict, &ent ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "hhProjectile: 'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( owner.GetEntity(), GetOrigin(), dir.ToMat3() ); + debris->Launch(); + } + } +} + +/* +================ +hhHarvesterMine::SetGravity +================ +*/ +void hhHarvesterMine::SetGravity( const idVec3 &newGravity ) { + float relativeMagnitude = spawnArgs.GetFloat( "gravity" ); + idVec3 newGravityVector( vec3_zero ); + + if( GetGravity().Compare(newGravity, VECTOR_EPSILON) ) { + return; + } + + if( relativeMagnitude > 0.0f ) { + newGravityVector = newGravity; + relativeMagnitude *= newGravityVector.Normalize() / gameLocal.GetGravity().Length(); + newGravityVector *= relativeMagnitude; + } + + GetPhysics()->SetGravity( newGravityVector ); +} + +/* +================ +hhHarvesterMine::Event_Explode +================ +*/ +void hhHarvesterMine::Event_Explode( void ) { + trace_t collision; + + SIMDProcessor->Memset( &collision, 0, sizeof(trace_t) ); + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + collision.c.entityNum = ENTITYNUM_WORLD; + collision.c.normal = idVec3(0.0f, 0.0f, 1.0f); + Explode( &collision ); +} + +/* +================ +hhHarvesterMine::Event_Detonate +================ +*/ +void hhHarvesterMine::Event_Detonate( idEntity *activator ) { + trace_t collision; + + //Monsters and other harvesters are culled out by the trigger behavior + if( owner != activator ) { + SIMDProcessor->Memset( &collision, 0, sizeof(trace_t) ); + collision.endpos = GetOrigin(); + collision.endAxis = GetAxis(); + if(!activator) { + collision.c.entityNum = ENTITYNUM_WORLD; + collision.c.normal = idVec3(0.0f, 0.0f, 1.0f); + } else { + collision.c.entityNum = activator->entityNumber; + collision.c.normal = GetOrigin() - activator->GetOrigin(); + collision.c.normal.Normalize(); + } + + Explode( &collision ); + } +} + +/* +================ +hhHarvesterMine::Event_ApplyAttractionTowards +================ +*/ +void hhHarvesterMine::Event_ApplyAttractionTowards( idEntity *activator ) { + if( !activator || owner == activator ) { + return; + } + + idVec3 dirToTarget = ((activator->IsType(idActor::Type)) ? static_cast(activator)->GetEyePosition() : activator->GetOrigin()) - GetOrigin(); + dirToTarget.Normalize(); + + ApplyImpulse( this, 0, GetOrigin(), dirToTarget * spawnArgs.GetFloat("attractionMagnitude") ); +} + +/* +================ +hhHarvesterMine::Save +================ +*/ +void hhHarvesterMine::Save( idSaveGame *savefile ) const { + proximityDetonateTrigger.Save( savefile ); + proximityAttractionTrigger.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + + owner.Save( savefile ); + + savefile->WriteRenderLight( renderLight ); + savefile->WriteVec3( lightOffset ); + savefile->WriteInt( lightStartTime ); + savefile->WriteInt( lightEndTime ); + savefile->WriteVec3( lightColor ); + savefile->WriteInt( state ); +} + +/* +================ +hhHarvesterMine::Restore +================ +*/ +void hhHarvesterMine::Restore( idRestoreGame *savefile ) { + proximityDetonateTrigger.Restore( savefile ); + proximityAttractionTrigger.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + owner.Restore( savefile ); + + savefile->ReadRenderLight( renderLight ); + savefile->ReadVec3( lightOffset ); + savefile->ReadInt( lightStartTime ); + savefile->ReadInt( lightEndTime ); + savefile->ReadVec3( lightColor ); + savefile->ReadInt( reinterpret_cast ( state ) ); +} diff --git a/neo/prey/Prey/prey_projectilemine.h b/neo/prey/Prey/prey_projectilemine.h new file mode 100644 index 00000000..0cd0e064 --- /dev/null +++ b/neo/prey/Prey/prey_projectilemine.h @@ -0,0 +1,70 @@ +#ifndef __HH_PROJECTILE_MINE_H +#define __HH_PROJECTILE_MINE_H + +/*********************************************************************** + + hhProjectileMine + +***********************************************************************/ +class hhHarvesterMine : public hhRenderEntity { + CLASS_PROTOTYPE( hhHarvesterMine ); + + public: + hhHarvesterMine(); + virtual ~hhHarvesterMine(); + void Spawn(); + + virtual void Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ); + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity ); + virtual bool Collide( const trace_t& collision, const idVec3& velocity ); + + virtual void SetGravity( const idVec3 &newGravity ); + + protected: + virtual void SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ); + virtual void RemoveProjectile( const int removeDelay ); + virtual idEntity* DetermineClipModelOwner(); + virtual void SpawnCollisionFX( const trace_t* collision, const char* fxKey ); + + virtual void Explode( const trace_t *collision ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual int DetermineContents(); + virtual void InitPhysics( const idVec3& start, const idMat3& axis, const idVec3& pushVelocity ); + void SpawnTriggers(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + void Event_Detonate( idEntity *activator ); + void Event_ApplyAttractionTowards( idEntity *activator ); + void Event_Explode(); + + protected: + idEntityPtr proximityDetonateTrigger; + idEntityPtr proximityAttractionTrigger; + + hhPhysics_RigidBodySimple physicsObj; + + idEntityPtr owner; + + renderLight_t renderLight; + //qhandle_t lightDefHandle; // handle to renderer light def + idVec3 lightOffset; + int lightStartTime; + int lightEndTime; + idVec3 lightColor; + + typedef enum { + SPAWNED = 0, + CREATED = 1, + LAUNCHED = 2, + FIZZLED = 3, + EXPLODED = 4 + } mineState_t; + + mineState_t state; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilerifle.cpp b/neo/prey/Prey/prey_projectilerifle.cpp new file mode 100644 index 00000000..791b9952 --- /dev/null +++ b/neo/prey/Prey/prey_projectilerifle.cpp @@ -0,0 +1,226 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileRifleSniper + +***********************************************************************/ +const idEventDef EV_AttemptFinalExitEventDef( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileRifleSniper ) + EVENT( EV_AttemptFinalExitEventDef, hhProjectileRifleSniper::Event_AttemptFinalExitEventDef ) + EVENT( EV_AllowCollision_Flesh, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Wood, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Glass, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Pipe, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Metal, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_AltMetal, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_Tile, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) + EVENT( EV_AllowCollision_CardBoard, hhProjectileRifleSniper::Event_AllowCollision_PassThru ) +END_CLASS + +/* +================= +hhProjectileRifleSniper::Spawn +================= +*/ +void hhProjectileRifleSniper::Spawn() { + numPassThroughs = 0.0f; + maxPassThroughs = hhMath::hhMax( 1.0f, spawnArgs.GetFloat("maxPassThroughs") ); +} + +/* +================= +hhProjectileRifleSniper::Event_AllowCollision_PassThru +================= +*/ +void hhProjectileRifleSniper::Event_AllowCollision_PassThru( const trace_t* collision ) { + assert( collision ); + + idEntity *entityHit = gameLocal.entities[ collision->c.entityNum ]; + + if (!entityHit->IsType(idAI::Type)) { + //gameLocal.Printf("STOP: [%s]\n", entityHit->GetName()); + hhProjectile::Event_AllowCollision_Collide( collision ); + return; + } + + if( lastDamagedEntity != entityHit ) { + lastDamagedEntity = entityHit; + + if( numPassThroughs < maxPassThroughs ) { + idVec3 myVel = GetPhysics()->GetLinearVelocity(); + idVec3 otherVel = entityHit->GetPhysics()->GetLinearVelocity(); + idVec3 vel = myVel - otherVel; + DamageEntityHit( collision, vel, entityHit ); + + numPassThroughs = hhMath::hhMin( numPassThroughs + 1.0f, maxPassThroughs ); + if( numPassThroughs >= maxPassThroughs ) { + ProcessEvent( &EV_AttemptFinalExitEventDef ); + } + } + } + + //gameLocal.Printf("PASS: [%s]\n", entityHit->GetName()); + hhProjectile::Event_AllowCollision_PassThru( collision ); +} + +/* +================= +hhProjectileRifleSniper::DetermineDamageScale +================= +*/ +float hhProjectileRifleSniper::DetermineDamageScale( const trace_t* collision ) const { + float scale = 1.0f - (numPassThroughs / maxPassThroughs); + + return scale; +} + +/* +================= +hhProjectileRifleSniper::DamageIsValid +================= +*/ +bool hhProjectileRifleSniper::DamageIsValid( const trace_t* collision, float& damageScale ) { + if( numPassThroughs < maxPassThroughs && hhProjectile::DamageIsValid(collision, damageScale) ) { + return true; + } + + RemoveProjectile( 0 ); + return false; +} + +void hhProjectileRifleSniper::Think() { + hhProjectile::Think(); + + // Still check for projectile outside of world, since sometimes that can make it + // through, at which point they stop simulating, but still play sound, have fx_fly, etc. + if( !gameLocal.clip.GetWorldBounds().ContainsPoint(GetOrigin()) ) { + RemoveProjectile( 0 ); + BecomeInactive( TH_ALL ); + } +} + +/* +================= +hhProjectileRifleSniper::Event_AttemptFinalExitEventDef +================= +*/ +void hhProjectileRifleSniper::Event_AttemptFinalExitEventDef() { + //Allow the projectile to do something behind final object hit + + CancelEvents( &EV_AttemptFinalExitEventDef ); + if( lastDamagedEntity.IsValid() && !lastDamagedEntity->GetPhysics()->GetAbsBounds().IntersectsBounds(GetPhysics()->GetAbsBounds()) ) { + CancelEvents( &EV_Remove ); + PostEventMS( GetInvalidDamageEventDef(), 0 ); + return; + } + + PostEventMS( &EV_AttemptFinalExitEventDef, USERCMD_MSEC ); +} + +/* +================= +hhProjectileRifleSniper::DamageEntityHit +================= +*/ +void hhProjectileRifleSniper::DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ) { + float push = 0.0f; + float damageScale = 1.0f; + const char *damage = spawnArgs.GetString( "def_damage" ); + hhPlayer* playerHit = (entHit->IsType(hhPlayer::Type)) ? static_cast(entHit) : NULL; + idAFEntity_Base* afHit = (entHit->IsType(idAFEntity_Base::Type)) ? static_cast(entHit) : NULL; + + idVec3 dir = velocity.ToNormal(); + + // non-radius damage defs can also apply an additional impulse to the rigid body physics impulse + const idDeclEntityDef *def = gameLocal.FindEntityDef( damage, false ); + if ( def ) { + if (entHit->IsType(hhProjectile::Type)) { + push = 0.0f; // mdl: Don't let projectiles push each other + } else if (afHit && afHit->IsActiveAF() ) { + push = def->dict.GetFloat( "push_ragdoll" ); + } else { + push = def->dict.GetFloat( "push" ); + } + } + + if (!gameLocal.isClient) { //rww + if( playerHit ) { + // pdm: save collision location in case we want to project a blob there + playerHit->playerView.SetDamageLoc( collision->endpos ); + } + + if ( entHit && entHit->IsType( idAI::Type ) ) { + idAI *aiHit = static_cast(entHit); + if ( aiHit && aiHit->InVehicle() ) { + idEntity *killer = owner.GetEntity(); + hhVehicle *vehicle = aiHit->GetVehicleInterface()->GetVehicle(); + if ( vehicle ) { + //use a different damagedef, since we cant affect the damage amount from here directly + damage = spawnArgs.GetString( "def_pilotdamage" ); + vehicle->Damage( this, killer, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(collision->c.id) ); + } + } + } + + if( DamageIsValid(collision, damageScale) && entHit->fl.takedamage ) { + UpdateBalanceInfo( collision, entHit ); + + if( damage && damage[0] ) { + idEntity *killer = owner.GetEntity(); + if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people + hhVehicle *veh = static_cast(killer); + if (veh->GetPilot()) { + killer = veh->GetPilot(); + } + } + + entHit->Damage( this, killer, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(collision->c.id) ); + + if ( playerHit && def->dict.GetInt( "freeze_duration" ) > 0 ) { + playerHit->Freeze( def->dict.GetInt( "freeze_duration" ) ); + } + } + } + + // HUMANHEAD bjk: moved to after damage so impulse can be applied to ragdoll + if ( push > 0.0f ) { + if (g_debugImpulse.GetBool()) { + gameRenderWorld->DebugArrow(colorYellow, collision->c.point, collision->c.point + (push*dir), 25, 2000); + } + + entHit->ApplyImpulse( this, collision->c.id, collision->c.point, push * dir ); + } + } + + if ( entHit->fl.applyDamageEffects ) { + ApplyDamageEffect( entHit, collision, velocity, damage ); + } +} + +/* +================ +hhProjectileRifleSniper::Save +================ +*/ +void hhProjectileRifleSniper::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( numPassThroughs ); + savefile->WriteFloat( maxPassThroughs ); + lastDamagedEntity.Save( savefile ); +} + +/* +================ +hhProjectileRifleSniper::Restore +================ +*/ +void hhProjectileRifleSniper::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( numPassThroughs ); + savefile->ReadFloat( maxPassThroughs ); + lastDamagedEntity.Restore( savefile ); +} + diff --git a/neo/prey/Prey/prey_projectilerifle.h b/neo/prey/Prey/prey_projectilerifle.h new file mode 100644 index 00000000..b87af636 --- /dev/null +++ b/neo/prey/Prey/prey_projectilerifle.h @@ -0,0 +1,41 @@ +#ifndef __HH_PROJECTILE_RIFLE_SNIPER_H +#define __HH_PROJECTILE_RIFLE_SNIPER_H + +/*********************************************************************** + + hhProjectileRifleSniper + +***********************************************************************/ +extern const idEventDef EV_ApplyExitWound; +extern const idEventDef EV_AttemptFinalExitEventDef; + +class hhProjectileRifleSniper : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileRifleSniper ); + + public: + void Spawn(); + virtual void Think(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual float DetermineDamageScale( const trace_t* collision ) const; + virtual bool DamageIsValid( const trace_t* collision, float& damageScale ); + + virtual const idEventDef* GetInvalidDamageEventDef() const { return &EV_Fizzle; } + void DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ); + + protected: + void Event_AttemptFinalExitEventDef(); + + void Event_AllowCollision_PassThru( const trace_t* collision ); + + protected: + //Used floats so we can divide without casting + float numPassThroughs; + float maxPassThroughs; + idEntityPtr lastDamagedEntity; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilerocketlauncher.cpp b/neo/prey/Prey/prey_projectilerocketlauncher.cpp new file mode 100644 index 00000000..9aa620f4 --- /dev/null +++ b/neo/prey/Prey/prey_projectilerocketlauncher.cpp @@ -0,0 +1,323 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileRocketLauncher + +***********************************************************************/ +const idEventDef EV_SpawnModelProxyLocal( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileRocketLauncher ) + EVENT( EV_SpawnModelProxyLocal, hhProjectileRocketLauncher::Event_SpawnModelProxyLocal ) + EVENT( EV_SpawnFxFlyLocal, hhProjectileRocketLauncher::Event_SpawnFxFlyLocal ) + + EVENT( EV_Collision_Flesh, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileRocketLauncher::Event_Collision_Explode ) + //EVENT( EV_Collision_Chaff, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileRocketLauncher::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileRocketLauncher::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileRocketLauncher::Event_AllowCollision_Collide ) +END_CLASS + +/* +================ +hhProjectileRocketLauncher::Spawn +================ +*/ +void hhProjectileRocketLauncher::Spawn() { +} + +/* +================ +hhProjectileRocketLauncher::~hhProjectileRocketLauncher +================ +*/ +hhProjectileRocketLauncher::~hhProjectileRocketLauncher() { + SAFE_REMOVE( modelProxy ); +} + +/* +================= +hhProjectileRocketLauncher::Hide +================= +*/ +void hhProjectileRocketLauncher::Hide() { + hhProjectile::Hide(); + + if( modelProxy.IsValid() ) { + modelProxy->Hide(); + } +} + +/* +================= +hhProjectileRocketLauncher::Show +================= +*/ +void hhProjectileRocketLauncher::Show() { + hhProjectile::Show(); + + if( modelProxy.IsValid() ) { + modelProxy->Show(); + } +} + +/* +================= +hhProjectileRocketLauncher::RemoveProjectile +================= +*/ +void hhProjectileRocketLauncher::RemoveProjectile( const int removeDelay ) { + hhProjectile::RemoveProjectile( removeDelay ); + + if( modelProxy.IsValid() ) { + modelProxy->PostEventMS( &EV_Remove, removeDelay ); + } +} + +/* +================ +hhProjectileRocketLauncher::Launch +================ +*/ +void hhProjectileRocketLauncher::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhFxInfo fxInfo; + + ProcessEvent( &EV_SpawnModelProxyLocal ); //rww - was using BroadcastEventDef + + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + if( modelProxy.IsValid() ) { + fxInfo.SetEntity( this ); + modelProxy->BroadcastFxInfoAlongBonePrefix( &spawnArgs, "fx_blood", "joint_bloodFx", false ); //rww - don't broadcast + } +} + +/* +================ +hhProjectileRocketLauncher::Event_SpawnFxFlyLocal +================ +*/ +void hhProjectileRocketLauncher::Event_SpawnFxFlyLocal( const char* defName ) { + hhFxInfo fxInfo; + + if( modelProxy.IsValid() ) { + fxInfo.SetEntity( this ); + modelProxy->SpawnFxAlongBonePrefixLocal( &spawnArgs, "fx_fly", "joint_flyFx", &fxInfo ); + } +} + +/* +================= +hhProjectileRocketLauncher::Event_SpawnModelProxyLocal +================= +*/ +void hhProjectileRocketLauncher::Event_SpawnModelProxyLocal() { + idDict args = spawnArgs; + + static const idMat3 pitchedOverAxis( idAngles(-90.0f, 0.0f, 0.0f).ToMat3() ); + + args.Delete( "spawnclass" ); + args.Delete( "name" ); + args.Delete( "spawn_entnum" ); //HUMANHEAD rww - yeah, might not be smart to try to spawn in the same entity slot. + + args.Set( "owner", GetName() ); + args.SetVector( "origin", GetOrigin() ); + args.SetMatrix( "rotation", pitchedOverAxis * GetAxis() ); + args.SetBool( "transferDamage", false ); + args.SetBool( "solid", false ); + if (!modelProxy.IsValid()) { + modelProxy = gameLocal.SpawnEntityTypeClient( hhGenericAnimatedPart::Type, &args ); //rww - proxy now localized + if( modelProxy.IsValid() ) { + modelProxy->fl.networkSync = false; + modelProxy->Bind( this, true ); + modelProxy->CycleAnim( "idle", ANIMCHANNEL_ALL ); + } + } + + //Get rid of our model. The modelProxy is our model now. + if (!gameLocal.isClient) { + SetModel( "" ); + } +} + +/* +================ +hhProjectileRocketLauncher::Save +================ +*/ +void hhProjectileRocketLauncher::Save( idSaveGame *savefile ) const { + modelProxy.Save( savefile ); +} + +/* +================ +hhProjectileRocketLauncher::Restore +================ +*/ +void hhProjectileRocketLauncher::Restore( idRestoreGame *savefile ) { + modelProxy.Restore( savefile ); +} + +void hhProjectileRocketLauncher::ClientPredictionThink( void ) { + if (!gameLocal.isNewFrame) { //HUMANHEAD rww + return; + } + + //rww - this code is duplicated here since the rocket projectile on the client is a little special-cased + // HUMANHEAD: cjr - if this projectile recently struck a portal, then attempt to portal it + if ( (thinkFlags & TH_MISC1) && collidedPortal.IsValid() ) { + GetPhysics()->SetLinearVelocity( collideVelocity ); + collidedPortal->PortalProjectile( this, collideLocation, collideLocation + collideVelocity ); + collidedPortal = NULL; + collideLocation = vec3_origin; + collideVelocity = vec3_origin; + BecomeInactive(TH_MISC1); + } + // HUMANHEAD END + + RunPhysics(); + + if ( thinkFlags & TH_MISC2 ) { + UpdateLight(); + } +} + +/* +================ +hhProjectileRocketLauncher::Event_AllowCollision_Collide +================ +*/ +void hhProjectileRocketLauncher::Event_AllowCollision_Collide( const trace_t* collision ) { + idThread::ReturnInt( 1 ); +} + +/*********************************************************************** + + hhProjectileChaff + +***********************************************************************/ +const idEventDef EV_CollidedWithChaff( "", "tv", 'd' ); +CLASS_DECLARATION( hhProjectile, hhProjectileChaff ) + EVENT( EV_Touch, hhProjectileChaff::Event_Touch ) + //FIXME: null out all of the events +END_CLASS + +/* +================ +hhProjectileChaff::Spawn +================ +*/ +void hhProjectileChaff::Spawn() { + decelStart = SEC2MS( spawnArgs.GetFloat("decelStart") ) + gameLocal.GetTime(); + decelEnd = SEC2MS( spawnArgs.GetFloat("decelDuration") ) + decelStart; + + BecomeActive( TH_TICKER ); +} + +/* +================ +hhProjectileChaff::Launch +================ +*/ +void hhProjectileChaff::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + cachedVelocity = GetPhysics()->GetLinearVelocity(); + + fl.takedamage = false; + physicsObj.DisableImpact(); +} + +/* +================ +hhProjectileChaff::Collide +================ +*/ +bool hhProjectileChaff::Collide( const trace_t& collision, const idVec3& velocity ) { + // Let the target know + idEntity *ent = gameLocal.GetTraceEntity( collision ); + if ( ent && ent->RespondsTo( EV_CollidedWithChaff ) ) { + ent->PostEventMS( &EV_CollidedWithChaff, 0, &collision, velocity ); + } + + return true;//Always stop after collision +} + +/* +================ +hhProjectileChaff::DetermineContents +================ +*/ +int hhProjectileChaff::DetermineContents() { + // Removed PROJECTILE + return CONTENTS_BLOCK_RADIUSDAMAGE | CONTENTS_OWNER_TO_OWNER | CONTENTS_SHOOTABLE; +} + +/* +================ +hhProjectileChaff::DetermineClipmask +================ +*/ +int hhProjectileChaff::DetermineClipmask() { + // Removed SHOOTABLE, added PROJECTILE + return CONTENTS_PROJECTILE|CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_CORPSE|CONTENTS_WATER|CONTENTS_FORCEFIELD; +} + + +/* +================ +hhProjectileChaff::Ticker +================ +*/ +void hhProjectileChaff::Ticker() { + float scale = 0.0f; + if( gameLocal.GetTime() > decelStart && gameLocal.GetTime() < decelEnd ) { + scale = hhMath::Sin( DEG2RAD(hhMath::MidPointLerp( 0.0f, 30.0f, 90.0f, 1.0f - hhUtils::CalculateScale(gameLocal.GetTime(), decelStart, decelEnd))) ); + + GetPhysics()->SetLinearVelocity( cachedVelocity * hhMath::ClampFloat(0.05f, 1.0f, scale) ); + } +} + +/* +================ +hhProjectileChaff::Event_Touch +================ +*/ +void hhProjectileChaff::Event_Touch( idEntity *other, trace_t *trace ) { + //Supposed to be empty +} + +/* +================ +hhProjectileChaff::Save +================ +*/ +void hhProjectileChaff::Save( idSaveGame *savefile ) const { + savefile->WriteInt( decelStart ); + savefile->WriteInt( decelEnd ); + savefile->WriteVec3( cachedVelocity ); +} + +/* +================ +hhProjectileChaff::Restore +================ +*/ +void hhProjectileChaff::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( decelStart ); + savefile->ReadInt( decelEnd ); + savefile->ReadVec3( cachedVelocity ); +} diff --git a/neo/prey/Prey/prey_projectilerocketlauncher.h b/neo/prey/Prey/prey_projectilerocketlauncher.h new file mode 100644 index 00000000..4b4cd04d --- /dev/null +++ b/neo/prey/Prey/prey_projectilerocketlauncher.h @@ -0,0 +1,70 @@ +#ifndef __HH_PROJECTILE_ROCKET_LAUNCHER_H +#define __HH_PROJECTILE_ROCKET_LAUNCHER_H + +/*********************************************************************** + + hhProjectileRocketLauncher + +***********************************************************************/ +class hhProjectileRocketLauncher : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileRocketLauncher ); + + public: + void Spawn(); + virtual ~hhProjectileRocketLauncher(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + + virtual void Hide(); + virtual void Show(); + virtual void RemoveProjectile( const int removeDelay ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientPredictionThink( void ); + + protected: + void Event_SpawnModelProxyLocal(); + void Event_SpawnFxFlyLocal( const char* defName ); + + void Event_AllowCollision_Collide( const trace_t* collision ); + + protected: + idEntityPtr modelProxy; +}; + +/*********************************************************************** + + hhProjectileChaff + +***********************************************************************/ +class hhProjectileChaff : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileChaff ); + + public: + void Spawn(); + + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + virtual bool Collide( const trace_t& collision, const idVec3& velocity ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual void Ticker(); + + virtual int DetermineContents(); + virtual int DetermineClipmask(); + + protected: + void Event_Touch( idEntity *other, trace_t *trace ); + + protected: + int decelStart; + int decelEnd; + + idVec3 cachedVelocity; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectileshuttle.cpp b/neo/prey/Prey/prey_projectileshuttle.cpp new file mode 100644 index 00000000..1d39aaa8 --- /dev/null +++ b/neo/prey/Prey/prey_projectileshuttle.cpp @@ -0,0 +1,22 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileShuttle ) + EVENT( EV_Collision_Flesh, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Chaff, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileShuttle::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileShuttle::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileShuttle::Event_AllowCollision_Collide ) +END_CLASS \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectileshuttle.h b/neo/prey/Prey/prey_projectileshuttle.h new file mode 100644 index 00000000..7bdfa8e3 --- /dev/null +++ b/neo/prey/Prey/prey_projectileshuttle.h @@ -0,0 +1,8 @@ +#ifndef __HH_PROJECTILE_SHUTTLE_H +#define __HH_PROJECTILE_SHUTTLE_H + +class hhProjectileShuttle : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileShuttle ) +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilesoulcannon.cpp b/neo/prey/Prey/prey_projectilesoulcannon.cpp new file mode 100644 index 00000000..cacf5090 --- /dev/null +++ b/neo/prey/Prey/prey_projectilesoulcannon.cpp @@ -0,0 +1,143 @@ +#include "precompiled.h" +#pragma hdrstop + +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileSoulCannon + +***********************************************************************/ + +const idEventDef EV_FindSoulEnemy( "", NULL ); + +CLASS_DECLARATION( hhProjectile, hhProjectileSoulCannon ) + EVENT( EV_FindSoulEnemy, hhProjectileSoulCannon::Event_FindEnemy ) +END_CLASS + +//============================================================================= +// +// hhProjectileSoulCannon::Spawn +// +//============================================================================= + +void hhProjectileSoulCannon::Spawn() { + hhProjectile::Spawn(); + + BecomeActive( TH_THINK ); + + PostEventSec( &EV_FindSoulEnemy, 0.5f ); // Find an enemy shortly after being launched + + maxVelocity = spawnArgs.GetFloat( "maxVelocity", "400" ); + maxEnemyDist = spawnArgs.GetFloat( "maxEnemyDist", "4096" ); + + thrustDir = vec3_origin; +} + +//============================================================================= +// +// hhProjectileSoulCannon::Think +// +//============================================================================= + +void hhProjectileSoulCannon::Think( void ) { + // run physics + RunPhysics(); + + // Thrust toward enemy + if ( thinkFlags & TH_THINK && thrustDir != vec3_origin ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += thrustDir * spawnArgs.GetFloat( "soulThrust", "5.0" ); + + if ( vel.Length() > maxVelocity ) { // Cap the velocity + vel.Normalize(); + vel *= maxVelocity; + } + + GetPhysics()->SetLinearVelocity( vel ); + GetPhysics()->SetAxis( vel.ToMat3() ); + } + + //HUMANHEAD: aob + if (thinkFlags & TH_TICKER) { + Ticker(); + } + //HUMANHEAD + + Present(); +} + +//============================================================================= +// +// hhProjectileSoulCannon::Event_FindEnemy +// +// Finds a new enemy every second +//============================================================================= + +void hhProjectileSoulCannon::Event_FindEnemy( void ) { + int i; + idEntity *ent; + idActor *actor; + trace_t tr; + + PostEventSec( &EV_FindSoulEnemy, 1.0f ); + + for ( i = 0; i < gameLocal.num_entities; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idActor::Type ) ) { + continue; + } + + if ( ent == this || ent == this->GetOwner() ) { + continue; + } + + // Ignore dormant entities! + if( ent->IsHidden() || ent->fl.isDormant ) { // HUMANHEAD JRM - changed to fl.isDormant + continue; + } + + actor = static_cast( ent ); + if ( ( actor->health <= 0 ) ) { + continue; + } + + idVec3 center = actor->GetPhysics()->GetAbsBounds().GetCenter(); + + // Cannot see this enemy because he is too far away + if ( maxEnemyDist > 0.0f && ( center - GetOrigin() ).LengthSqr() > maxEnemyDist * maxEnemyDist ) + continue; + + // Quick trace to the center of the potential enemy + if ( !gameLocal.clip.TracePoint( tr, GetOrigin(), center, 1.0f, this ) ) { + thrustDir = center - GetOrigin(); + thrustDir.Normalize(); + break; + } + } +} + +/* +================ +hhProjectileSoulCannon::Save +================ +*/ +void hhProjectileSoulCannon::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( maxVelocity ); + savefile->WriteFloat( maxEnemyDist ); + savefile->WriteVec3( thrustDir ); +} + +/* +================ +hhProjectileSoulCannon::Restore +================ +*/ +void hhProjectileSoulCannon::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( maxVelocity ); + savefile->ReadFloat( maxEnemyDist ); + savefile->ReadVec3( thrustDir ); +} +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilesoulcannon.h b/neo/prey/Prey/prey_projectilesoulcannon.h new file mode 100644 index 00000000..1e7b1c56 --- /dev/null +++ b/neo/prey/Prey/prey_projectilesoulcannon.h @@ -0,0 +1,30 @@ +#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build +#ifndef __HH_PROJECTILE_SOUL_CANNON_H +#define __HH_PROJECTILE_SOUL_CANNON_H + +/*********************************************************************** + + hhProjectileSoulCannon + +***********************************************************************/ + +class hhProjectileSoulCannon : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileSoulCannon ); + + public: + void Spawn(); + void Think( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + float maxVelocity; + float maxEnemyDist; + idVec3 thrustDir; + + void hhProjectileSoulCannon::Event_FindEnemy( void ); +}; + +#endif +#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilespiritarrow.cpp b/neo/prey/Prey/prey_projectilespiritarrow.cpp new file mode 100644 index 00000000..7f19cb45 --- /dev/null +++ b/neo/prey/Prey/prey_projectilespiritarrow.cpp @@ -0,0 +1,105 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileSpiritArrow + +***********************************************************************/ +CLASS_DECLARATION( hhProjectile, hhProjectileSpiritArrow ) + EVENT( EV_AllowCollision_Spirit, hhProjectile::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Chaff, hhProjectile::Event_AllowCollision_PassThru ) + + EVENT( EV_AllowCollision_Flesh, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Metal, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_AltMetal, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Wood, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Stone, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Glass, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Pipe, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_Tile, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + EVENT( EV_AllowCollision_CardBoard, hhProjectileSpiritArrow::Event_AllowCollision_Collide ) + + EVENT( EV_Collision_Flesh, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Metal, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_AltMetal, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Wood, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Stone, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Glass, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Wallwalk, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Pipe, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_CardBoard, hhProjectileSpiritArrow::Event_Collision_Stick ) + EVENT( EV_Collision_Spirit, hhProjectileSpiritArrow::Event_Collision_Impact ) + EVENT( EV_Collision_Chaff, hhProjectileSpiritArrow::Event_Collision_Impact ) +END_CLASS + + +int hhProjectileSpiritArrow::DetermineClipmask() { + return MASK_SPIRITARROW; +} + +/* +================= +hhProjectileSpiritArrow::BindToCollisionObject +================= +*/ +void hhProjectileSpiritArrow::BindToCollisionObject( const trace_t* collision ) { + if( !collision || collision->fraction == 1.0f ) { + return; + } + + idEntity* pEntity = gameLocal.entities[collision->c.entityNum]; + HH_ASSERT( pEntity ); + + if( !pEntity->fl.applyDamageEffects ) { + PostEventMS( &EV_Fizzle, 0 ); + return; + } + + if ( pEntity->spawnArgs.GetBool( "no_arrow_stick" ) ) { + RemoveProjectile( 0 ); + return; + } + + jointHandle_t jointHandle = CLIPMODEL_ID_TO_JOINT_HANDLE( collision->c.id ); + idVec3 penetrationVector( collision->endAxis[0] * spawnArgs.GetFloat("penetrationDepth", "10") ); // Push the arrow into the hit object a bit + if ( jointHandle != INVALID_JOINT ) { + SetOrigin( collision->endpos + penetrationVector ); + BindToJoint( pEntity, jointHandle, true ); + } else { + SetOrigin( collision->endpos + penetrationVector ); + Bind( pEntity, true ); + } +} + +/* +================= +hhProjectileSpiritArrow::Event_Collision_Stick +================= +*/ +void hhProjectileSpiritArrow::Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ) { + + ProcessCollision( collision, velocity );//Assuming that EV_Fizzle is canceled in ProcessCollision + + if (gameLocal.isMultiplayer) { //rww - in mp we don't stick to players. + idEntity *pEntity = gameLocal.entities[collision->c.entityNum]; + if (pEntity && pEntity->IsType(hhPlayer::Type)) { + PostEventMS( &EV_Fizzle, 0 ); + + idThread::ReturnInt( 1 ); + return; + } + } + + PostEventMS( &EV_Fizzle, spawnArgs.GetInt("remove_time") ); + + BindToCollisionObject( collision ); + + fl.ignoreGravityZones = true; + SetGravity( idVec3(0.f, 0.f, 0.f) ); + spawnArgs.SetVector("gravity", idVec3(0.f, 0.f, 0.f) ); + + idThread::ReturnInt( 1 ); +} \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilespiritarrow.h b/neo/prey/Prey/prey_projectilespiritarrow.h new file mode 100644 index 00000000..b2f0e311 --- /dev/null +++ b/neo/prey/Prey/prey_projectilespiritarrow.h @@ -0,0 +1,20 @@ +#ifndef __HH_PROJECTILE_SPIRITARROW_H +#define __HH_PROJECTILE_SPIRITARROW_H + +/*********************************************************************** + + hhProjectileSpiritArrow + +***********************************************************************/ +class hhProjectileSpiritArrow: public hhProjectile { + CLASS_PROTOTYPE( hhProjectileSpiritArrow ); + + protected: + virtual int DetermineClipmask(); + void BindToCollisionObject( const trace_t* collision ); + + protected: + void Event_Collision_Stick( const trace_t* collision, const idVec3 &velocity ); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectiletracking.cpp b/neo/prey/Prey/prey_projectiletracking.cpp new file mode 100644 index 00000000..b8b29d81 --- /dev/null +++ b/neo/prey/Prey/prey_projectiletracking.cpp @@ -0,0 +1,330 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhProjectileTracking + +***********************************************************************/ +const idEventDef EV_Guide( "" ); +const idEventDef EV_Hover( "" ); +const idEventDef EV_StartTracking( "" ); +const idEventDef EV_StopTracking( "" ); + +CLASS_DECLARATION( hhProjectile, hhProjectileTracking ) + EVENT( EV_Guide, hhProjectileTracking::Event_TrackTarget ) + EVENT( EV_Hover, hhProjectileTracking::Event_Hover ) + EVENT( EV_StartTracking, hhProjectileTracking::Event_StartTracking ) + EVENT( EV_StopTracking, hhProjectileTracking::Event_StopTracking ) + + EVENT( EV_Collision_Flesh, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Chaff, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileTracking::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileTracking::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileTracking::Event_AllowCollision_Collide ) +END_CLASS + + + +hhProjectileTracking::hhProjectileTracking() { + spinAngle = 0.0f; // Needs to be initialized before spawn since GetPhysicsToVisualTransform() is called before spawn +} + +/* +================ +hhProjectileTracking::Spawn +================ +*/ +void hhProjectileTracking::Spawn() { + angularVelocity.Zero(); + velocity.Zero(); + + cachedFovCos = idMath::Cos( DEG2RAD(spawnArgs.GetFloat("fov", "90")) ); + turnFactor = spawnArgs.GetFloat("turnfactor"); + updateRate = spawnArgs.GetInt("trackingUpdateRate"); + turnFactorAcc = spawnArgs.GetFloat( "turn_factor_accel", "1.1" ); + spinDelta = spawnArgs.GetFloat( "spin_delta" ); + + //rww - some things are hhProjectileTracking when they shouldn't be or don't have proper properties. if this is so, complain. + if (!updateRate) { + gameLocal.Warning("hhProjectileTracking with an updateRate of 0 (possible infinite event queue)."); + } +} + +/* +================ +hhProjectileTracking::IsEnemyValid +================ +*/ +bool hhProjectileTracking::EnemyIsValid( idEntity* ent ) const { + if ( ent && ent->IsType( idActor::Type ) ) { + idActor *entActor = static_cast(ent); + return ( entActor->GetHealth() > 0 && entActor->team!= 0 && !entActor->IsHidden() && gameLocal.InPlayerPVS(entActor) ); + } + return ( ent && ent->GetHealth() > 0 && !ent->IsHidden() && gameLocal.InPlayerPVS(ent) ); +} + +/* +================ +hhProjectileTracking::WhosClosest +================ +*/ +idEntity* hhProjectileTracking::WhosClosest( idEntity* possibleEnemy, idEntity* currentEnemy, float& currentEnemyDot, float& currentEnemyDist ) const { + if( !possibleEnemy ) { + return currentEnemy; + } + + idVec3 enemyDir = DetermineEnemyPosition( possibleEnemy ) - GetOrigin(); + float cachedDist = enemyDir.Normalize(); + float cachedDot = enemyDir * GetAxis()[0]; + + //AOB: Think about putting in tolerances for deciding which is better, + if( cachedDot > cachedFovCos && cachedDot > currentEnemyDot && cachedDist <= currentEnemyDist ) { + currentEnemyDot = cachedDot; + currentEnemyDist = cachedDist; + return possibleEnemy; + } + + return currentEnemy; +} + +/* +================ +hhProjectileTracking::DetermineEnemy +================ +*/ +idEntity* hhProjectileTracking::DetermineEnemy() { + idEntity* possibleEnemy = NULL; + float currentEnemyDot = 0.0f; + float currentEnemyDist = CM_MAX_TRACE_DIST; + idEntity* localEnemy = NULL; + + if ( spawnArgs.GetInt( "trackPlayersOnly", "0" ) ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + possibleEnemy = gameLocal.entities[i]; + if ( possibleEnemy && possibleEnemy->IsType( hhPlayer::Type ) ) { + hhPlayer *player = static_cast(possibleEnemy); + if ( player && player->IsSpiritOrDeathwalking() ) { + possibleEnemy = player->GetSpiritProxy(); + } + } + localEnemy = WhosClosest( possibleEnemy, localEnemy, currentEnemyDot, currentEnemyDist ); + } + } + + return localEnemy; + } + int num = hhMonsterAI::allSimpleMonsters.Num(); + for( int index = 0; index < num; ++index ) { + possibleEnemy = hhMonsterAI::allSimpleMonsters[ index ]; + if( EnemyIsValid(possibleEnemy) ) { + localEnemy = WhosClosest( possibleEnemy, localEnemy, currentEnemyDot, currentEnemyDist ); + } + } + + return localEnemy; +} + +/* +================ +hhProjectileTracking::Launch +================ +*/ +void hhProjectileTracking::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + hhProjectile::Launch( start, axis, pushVelocity, timeSinceFire, launchPower, dmgPower ); + + enemy = DetermineEnemy(); + + float randomStartSpread = spawnArgs.GetFloat( "randomStartSpread", "0" ); + if ( randomStartSpread > 0.0 ) { + velocity = spawnArgs.GetVector( "velocity" ).Length() * hhUtils::RandomSpreadDir( GetPhysics()->GetAxis(), 1.0 ); + } else { + velocity = spawnArgs.GetVector( "velocity" ); + } + angularVelocity = spawnArgs.GetAngles( "angular_velocity" ); + float trackDelay = spawnArgs.GetFloat( "trackDelay", "0" ); + if( trackDelay > 0.0 ) { + PostEventSec( &EV_Hover, spawnArgs.GetFloat( "trackStop", "0" ) ); + PostEventSec( &EV_StartTracking, trackDelay ); + } else if ( enemy.IsValid() ) { + PostEventMS( &EV_Guide, updateRate ); + } + float trackDuration = spawnArgs.GetFloat( "trackDuration", "0" ); + if ( trackDuration > 0.0 ) { + PostEventSec( &EV_StopTracking, trackDuration ); + } +} + +void hhProjectileTracking::Event_Hover() { + physicsObj.SetLinearVelocity( velocity * spawnArgs.GetFloat( "hoverScale", "0.15" ) ); +} + +void hhProjectileTracking::Event_StartTracking() { + float randomStartSpread = spawnArgs.GetFloat( "randomStartSpread", "0" ); + if ( randomStartSpread > 0.0 ) { + velocity = spawnArgs.GetVector( "velocity" ); + velocity.y = gameLocal.random.CRandomFloat() * randomStartSpread; + velocity.z = gameLocal.random.CRandomFloat() * randomStartSpread; + } + PostEventMS( &EV_Guide, updateRate ); +} + +void hhProjectileTracking::Event_StopTracking() { + CancelEvents(&EV_Guide); +} + +/* +================ +hhProjectileTracking::DetermineEnemyPosition +================ +*/ +idVec3 hhProjectileTracking::DetermineEnemyPosition( const idEntity* ent ) const { + if ( ent && ent->IsType( idActor::Type ) ) { + const idActor *entActor = static_cast(ent); + return entActor->GetEyePosition(); + } + + return ent->GetOrigin(); +} + +/* +================ +hhProjectileTracking::DetermineEnemyDir +================ +*/ +idVec3 hhProjectileTracking::DetermineEnemyDir( const idEntity* actor ) const { + idVec3 enemyPos = DetermineEnemyPosition( actor ); + idVec3 enemyDir = enemyPos - GetOrigin(); + enemyDir.Normalize(); + + return enemyDir; +} + +/* +================ +hhProjectileTracking::Explode +================ +*/ +void hhProjectileTracking::Explode( const trace_t *collision, const idVec3& velocity, int removeDelay ) { + hhProjectile::Explode( collision, velocity, removeDelay ); + CancelEvents(&EV_Guide); +} + +/* +================ +hhProjectileTracking::GetPhysicsToVisualTransform +================ +*/ +bool hhProjectileTracking::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( spinDelta != 0.0f ) { + axis = idAngles(0,0,spinAngle).ToMat3(); + spinAngle += spinDelta; + if ( spinAngle > 360.0f ) { + spinAngle = 0.0f; + } + return true; + } + + return hhProjectile::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +hhProjectileTracking::Event_TrackTarget +================ +*/ +void hhProjectileTracking::Event_TrackTarget() { + if ( !spawnArgs.GetInt( "constantEnemy", "1" ) ) { + enemy = DetermineEnemy(); + } + + if( !enemy.IsValid() ) { + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + return; + } + + if( !enemy->GetHealth() ) { + //if enemy is dead, just explode + trace_t collision; + memset( &collision, 0, sizeof( collision ) ); + collision.endAxis = GetPhysics()->GetAxis(); + collision.endpos = GetPhysics()->GetOrigin(); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + Explode( &collision, idVec3(0,0,0), 3 ); + } + + if ( turnFactor < 1.0 && turnFactorAcc > 1.0 ) { + turnFactor *= turnFactorAcc; // Accelerate the turn as time goes on so we don't get stuck in any orbits + } + idVec3 enemyDir = DetermineEnemyDir( enemy.GetEntity() ); + idVec3 currentDir = GetAxis()[0]; + idVec3 newDir = currentDir*(1-turnFactor) + enemyDir*turnFactor; + newDir.Normalize(); + if ( driver.IsValid() ) { + driver->SetAxis(newDir.ToMat3()); + } else { + SetAxis(newDir.ToMat3()); + } + + physicsObj.SetLinearVelocity( GetAxis()[ 0 ] * velocity[ 0 ] + GetAxis()[ 1 ] * velocity[ 1 ] + GetAxis()[ 2 ] * velocity[ 2 ] ); + physicsObj.SetAngularVelocity( angularVelocity.ToAngularVelocity() * GetAxis() ); + + PostEventMS( &EV_Guide, updateRate ); +} + +/* +================ +hhProjectileTracking::Save +================ +*/ +void hhProjectileTracking::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( turnFactor ); + savefile->WriteInt( updateRate ); + + enemy.Save( savefile ); + + savefile->WriteAngles( angularVelocity ); + savefile->WriteVec3( velocity ); + savefile->WriteFloat( cachedFovCos ); + savefile->WriteFloat( spinAngle ); + savefile->WriteFloat( turnFactorAcc ); + savefile->WriteFloat( spinDelta ); +} + +/* +================ +hhProjectileTracking::Restore +================ +*/ +void hhProjectileTracking::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( turnFactor ); + savefile->ReadInt( updateRate ); + + enemy.Restore( savefile ); + + savefile->ReadAngles( angularVelocity ); + savefile->ReadVec3( velocity ); + savefile->ReadFloat( cachedFovCos ); + savefile->ReadFloat( spinAngle ); + savefile->ReadFloat( turnFactorAcc ); + savefile->ReadFloat( spinDelta ); +} + +void hhProjectileTracking::StartTracking() { + Event_StartTracking(); +} \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectiletracking.h b/neo/prey/Prey/prey_projectiletracking.h new file mode 100644 index 00000000..f1076668 --- /dev/null +++ b/neo/prey/Prey/prey_projectiletracking.h @@ -0,0 +1,48 @@ +#ifndef __HH_PROJECTILE_TRACKING_H +#define __HH_PROJECTILE_TRACKING_H + +/*********************************************************************** + + hhProjectileTracking + +***********************************************************************/ +class hhProjectileTracking: public hhProjectile { + CLASS_PROTOTYPE( hhProjectileTracking ); + + public: + hhProjectileTracking(); + void Spawn(); + virtual void Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + virtual void Explode( const trace_t *collision, const idVec3& velocity, int removeDelay ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void StartTracking(); + void SetEnemy( idEntity* newEnemy ) { enemy = newEnemy; } + protected: + virtual idEntity* DetermineEnemy(); + bool EnemyIsValid( idEntity* actor ) const; + idEntity* WhosClosest( idEntity* possibleEnemy, idEntity* currentEnemy, float& currentEnemyDot, float& currentEnemyDist ) const; + idVec3 DetermineEnemyPosition( const idEntity* enemy ) const; + idVec3 DetermineEnemyDir( const idEntity* enemy ) const; + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + protected: + void Event_Hover(); + virtual void Event_TrackTarget(); + void Event_StartTracking(); + void Event_StopTracking(); + + protected: + float turnFactor; + int updateRate; + + idEntityPtr enemy; + idAngles angularVelocity; + idVec3 velocity; + float cachedFovCos; + float spinAngle; + float turnFactorAcc; + float spinDelta; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectiletrigger.cpp b/neo/prey/Prey/prey_projectiletrigger.cpp new file mode 100644 index 00000000..55b4b861 --- /dev/null +++ b/neo/prey/Prey/prey_projectiletrigger.cpp @@ -0,0 +1,56 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileTrigger ) + EVENT( EV_Collision_Flesh, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Metal, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_AltMetal, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Wood, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Stone, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Glass, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Liquid, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_CardBoard, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Tile, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Forcefield, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Chaff, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Pipe, hhProjectileTrigger::Event_Collision_Explode ) + EVENT( EV_Collision_Wallwalk, hhProjectileTrigger::Event_Collision_Explode ) + + EVENT( EV_AllowCollision_Chaff, hhProjectileTrigger::Event_AllowCollision_Collide ) +END_CLASS + +void hhProjectileTrigger::Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ) { + ProcessCollision(collision, velocity); + if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { + return; + } + if( !collision ) { + return; + } + int length = 0; + idEntity *trigger; + idDict Args; + + Args.Set( "def_damage", spawnArgs.GetString("trigger_damage") ); + Args.Set( "mins", spawnArgs.GetString("trigger_min") ); + Args.Set( "maxs", spawnArgs.GetString("trigger_max") ); + Args.Set( "snd_loop", spawnArgs.GetString("snd_loop") ); + Args.Set( "snd_explode", spawnArgs.GetString("snd_loop") ); + Args.SetVector( "origin", GetOrigin() ); + Args.SetMatrix( "rotation", GetAxis() ); + trigger = gameLocal.SpawnObject( spawnArgs.GetString("def_trigger"), &Args ); + if ( trigger ) { + trigger->PostEventSec( &EV_Remove, spawnArgs.GetFloat( "remove_time", "5" ) ); + trigger->PostEventSec( &EV_StopSound, spawnArgs.GetFloat( "remove_time", "5" ) - 1, SND_CHANNEL_ANY, 0 ); + trigger->StartSound( "snd_explode", SND_CHANNEL_VOICE, 0, true, &length ); + trigger->StartSound( "snd_loop", SND_CHANNEL_BODY, 0, true, &length ); + } + SpawnExplosionFx( collision ); + SpawnDebris( collision->c.normal, velocity.ToNormal() ); + state = EXPLODED; + RemoveProjectile( spawnArgs.GetFloat( "remove_time", "5" ) ); + ProcessCollision(collision, velocity); + idThread::ReturnInt( 1 ); +} diff --git a/neo/prey/Prey/prey_projectiletrigger.h b/neo/prey/Prey/prey_projectiletrigger.h new file mode 100644 index 00000000..cf90c207 --- /dev/null +++ b/neo/prey/Prey/prey_projectiletrigger.h @@ -0,0 +1,10 @@ +#ifndef __HH_PROJECTILE_TRIGGER_H +#define __HH_PROJECTILE_TRIGGER_H + +class hhProjectileTrigger: public hhProjectile { +public: + CLASS_PROTOTYPE( hhProjectileTrigger ) + void Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_projectilewrench.cpp b/neo/prey/Prey/prey_projectilewrench.cpp new file mode 100644 index 00000000..adc62936 --- /dev/null +++ b/neo/prey/Prey/prey_projectilewrench.cpp @@ -0,0 +1,43 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhProjectile, hhProjectileWrench ) + EVENT( EV_Collision_Flesh, hhProjectileWrench::Event_Collision_Impact ) + EVENT( EV_Collision_Metal, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_AltMetal, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Wood, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Stone, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Glass, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Liquid, hhProjectileWrench::Event_Collision_DisturbLiquid ) + EVENT( EV_Collision_CardBoard, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Tile, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Forcefield, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Pipe, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Wallwalk, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + EVENT( EV_Collision_Chaff, hhProjectileWrench::Event_Collision_Impact_AlertAI ) + +END_CLASS + +void hhProjectileWrench::Event_Collision_Impact_AlertAI( const trace_t* collision, const idVec3& velocity ) { + gameLocal.AlertAI( owner.GetEntity() ); + + CancelEvents( &EV_Explode ); + RemoveProjectile( ProcessCollision(collision, velocity) ); + state = COLLIDED; + idThread::ReturnInt( 1 ); +} + +void hhProjectileWrench::DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ) { + if (!gameLocal.isMultiplayer) { + hhPlayer* pOwner = (owner->IsType(hhPlayer::Type)) ? static_cast(owner.GetEntity()) : NULL; + + if ( entHit && entHit->IsType( idActor::Type ) ) { + //pOwner->weapon->SetSkinByName( "skins/weapons/wrench_bloody" ); + pOwner->weapon->SetShaderParm( 7, -MS2SEC(gameLocal.time) ); + } + } + hhProjectile::DamageEntityHit( collision, velocity, entHit ); +} + diff --git a/neo/prey/Prey/prey_projectilewrench.h b/neo/prey/Prey/prey_projectilewrench.h new file mode 100644 index 00000000..8132326f --- /dev/null +++ b/neo/prey/Prey/prey_projectilewrench.h @@ -0,0 +1,11 @@ +#ifndef __HH_PROJECTILE_WRENCH_H +#define __HH_PROJECTILE_WRENCH_H + +class hhProjectileWrench : public hhProjectile { + CLASS_PROTOTYPE( hhProjectileWrench ); + void Event_Collision_Impact_AlertAI( const trace_t* collision, const idVec3& velocity ); + protected: + void DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_script_thread.cpp b/neo/prey/Prey/prey_script_thread.cpp new file mode 100644 index 00000000..367f7691 --- /dev/null +++ b/neo/prey/Prey/prey_script_thread.cpp @@ -0,0 +1,114 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idThread, hhThread ) +END_CLASS + +hhThread::hhThread() : idThread() {} +hhThread::hhThread( idEntity *self, const function_t *func ) : idThread( self, func ) {} +hhThread::hhThread( const function_t *func ) : idThread( func ) {} +hhThread::hhThread( idInterpreter *source, const function_t *func, int args ) : idThread( source, func, args ) {} + +/* +================ +hhThread::PushParm +================ +*/ +void hhThread::PushParm( int value ) { + interpreter.Push( value ); +} + +/* +================ +hhThread::PushString +================ +*/ +void hhThread::PushString( const char *text ) { + PushParm( (int)new idStr(text) ); +} + +/* +================ +hhThread::PushFloat +================ +*/ +void hhThread::PushFloat( float value ) { + PushParm( *(int*)&value ); +} + +/* +================ +hhThread::PushInt +================ +*/ +void hhThread::PushInt( int value ) { + PushParm( value ); +} + +/* +================ +hhThread::PushVector +================ +*/ +void hhThread::PushVector( const idVec3 &vec ) { + float val = 0.0f; + for( int ix = 0; ix < vec.GetDimension(); ++ix ) { + val = vec[ ix ]; + PushParm( *(int*)&val ); + } +} + +/* +================ +hhThread::PushEntity +================ +*/ +void hhThread::PushEntity( const idEntity *ent ) { + HH_ASSERT( ent ); + + PushParm( ent->entityNumber + 1 ); +} + +/* +================ +hhThread::ClearStack +================ +*/ +void hhThread::ClearStack() { + interpreter.Reset(); +} + +/* +================ +hhThread::ParseAndPushArgsOntoStack +================ +*/ +bool hhThread::ParseAndPushArgsOntoStack( const idCmdArgs &args, const function_t* function ) { + idList parmList; + + hhUtils::SplitString( args, parmList ); + + return ParseAndPushArgsOntoStack( parmList, function ); +} + +/* +================ +hhThread::ParseAndPushArgsOntoStack +================ +*/ +bool hhThread::ParseAndPushArgsOntoStack( const idList& args, const function_t* function ) { + int numParms = function->def->TypeDef()->NumParameters(); + idTypeDef* parmType = NULL; + const char* parm = NULL; + + for( int ix = 0; ix < numParms; ++ix ) { + parmType = function->def->TypeDef()->GetParmType( ix ); + parm = args[ ix ].c_str(); + + parmType->PushOntoStack( parm, this ); + } + + return true; +} \ No newline at end of file diff --git a/neo/prey/Prey/prey_script_thread.h b/neo/prey/Prey/prey_script_thread.h new file mode 100644 index 00000000..f6e35822 --- /dev/null +++ b/neo/prey/Prey/prey_script_thread.h @@ -0,0 +1,27 @@ +#ifndef __HH_SCRIPT_THREAD_H__ +#define __HH_SCRIPT_THREAD_H__ + +class hhThread : public idThread { + CLASS_PROTOTYPE( hhThread ); + + public: + hhThread(); + hhThread( idEntity *self, const function_t *func ); + hhThread( const function_t *func ); + hhThread( idInterpreter *source, const function_t *func, int args ); + + void PushString( const char *text ); + void PushFloat( float value ); + void PushInt( int value ); + void PushVector( const idVec3 &vec ); + void PushEntity( const idEntity *ent ); + void ClearStack(); + + bool ParseAndPushArgsOntoStack( const idCmdArgs& args, const function_t* function ); + bool ParseAndPushArgsOntoStack( const idList& args, const function_t* function ); + + protected: + void PushParm( int value ); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_sound.cpp b/neo/prey/Prey/prey_sound.cpp new file mode 100644 index 00000000..0f883340 --- /dev/null +++ b/neo/prey/Prey/prey_sound.cpp @@ -0,0 +1,258 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_StartDelayedSoundShader( "", "sddd" ); +const idEventDef EV_ResetTargetHandles( "resetTargetHandles" ); +const idEventDef EV_SubtitleOff( "" ); + +CLASS_DECLARATION( idSound, hhSound ) + EVENT( EV_PostSpawn, hhSound::Event_SetTargetHandles ) + EVENT( EV_ResetTargetHandles, hhSound::Event_SetTargetHandles ) + EVENT( EV_SubtitleOff, hhSound::Event_SubtitleOff ) +END_CLASS + + +/* +================ +hhSound::hhSound +================ +*/ +hhSound::hhSound( void ) { + positionOffset.Zero(); +} + +/* +================ +hhSound::Spawn +================ +*/ +void hhSound::Spawn() { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +hhSound::StartDelayedSoundShader +================ +*/ +void hhSound::StartDelayedSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast ) { + CancelEvents( &EV_StartDelayedSoundShader ); + PostEventSec( &EV_StartDelayedSoundShader, RandomRange(spawnArgs.GetFloat("s_minDelay"), spawnArgs.GetFloat("s_maxDelay")), (const char *)shader->GetName(), (int)channel, (int)soundShaderFlags, (int)broadcast ); +} + +/* +================ +hhSound::StopDelayedSound +================ +*/ +void hhSound::StopDelayedSound( const s_channelType channel, bool broadcast ) { + CancelEvents( &EV_StartDelayedSoundShader ); + idSound::StopSound( channel, broadcast ); +} + +/* +================ +hhSound::StartSoundShader +================ +*/ +bool hhSound::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int* length ) { + bool result = true; + float volume = 0.0f; + + positionOffset = DeterminePositionOffset(); + if( !spawnArgs.GetBool("s_useRandomDelay") ) { + StopDelayedSound( channel, broadcast ); + + result = idSound::StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); + + const char *subtitleText = spawnArgs.GetString( "subtitle", NULL ); + if ( result && subtitleText ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( length > 0 && player && player->hud ) { + player->hud->SetStateInt("subtitlefadetime", 0); + player->hud->SetStateInt("subtitlex", 0 ); + player->hud->SetStateInt("subtitley", 400 ); + player->hud->SetStateInt("subtitlecentered", true); + player->hud->SetStateString("subtitletext", common->GetLanguageDict()->GetString(subtitleText)); + player->hud->StateChanged(gameLocal.time); + player->hud->HandleNamedEvent("DisplaySubtitle"); + PostEventMS( &EV_SubtitleOff, *length ); + } + } + + if( DetermineVolume(volume) ) { + HH_SetSoundVolume( volume, channel ); + } + } else { + StartDelayedSoundShader( shader, channel, soundShaderFlags, broadcast ); + } + + //trigger targets when sound ends + if ( length && targets.Num() && spawnArgs.GetInt("trigger_targets","0") ) { + float delay = *length * 0.001 + spawnArgs.GetFloat("target_delay", "0"); + if ( delay < 0 ) { + delay = 0; + } + PostEventSec( &EV_ActivateTargets, delay , this ); + } + + return result; +} + +/* +================ +hhSound::StopSound +================ +*/ +void hhSound::StopSound( const s_channelType channel, bool broadcast ) { + StopDelayedSound( channel, broadcast ); +} + +/* +================ +hhSound::RandomRange +================ +*/ +float hhSound::RandomRange( const float min, const float max ) { + return hhMath::Lerp( min, max, gameLocal.random.RandomFloat() ); +} + +/* +=============== +hhSound::DetermineVolume +=============== +*/ +bool hhSound::DetermineVolume( float& volume ) { + if( !spawnArgs.GetBool("s_useRandomVolume") ) { + return false; + } + + volume = hhMath::dB2Scale( RandomRange(spawnArgs.GetInt("s_minVolume"), spawnArgs.GetInt("s_maxVolume")) ); + return true; +} + +/* +=============== +hhSound::DeterminePositionOffset +=============== +*/ +idVec3 hhSound::DeterminePositionOffset() { + if( !spawnArgs.GetBool("s_useRandomPosition") ) { + return vec3_origin; + } + + float radius = RandomRange( spawnArgs.GetFloat("s_minRadius"), spawnArgs.GetFloat("s_maxRadius") ); + return hhUtils::RandomVector() * radius; +} + +/* +================ +hhSound::GetCurrentAmplitude +================ +*/ +float hhSound::GetCurrentAmplitude(const s_channelType channel) { + if (refSound.referenceSound && refSound.referenceSound->CurrentlyPlaying()) { + return refSound.referenceSound->CurrentAmplitude(/*channel*/); + } + return 0.0f; +} + +/* +================ +hhSound::GetPhysicsToSoundTransform +================ +*/ +bool hhSound::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = positionOffset; + axis.Identity(); + return true; +} + +/* +================ +hhSound::Event_StartDelayedSoundShader +================ +*/ +void hhSound::Event_StartDelayedSoundShader( const char *shaderName, const s_channelType channel, int soundShaderFlags, bool broadcast ) { + int soundLength = 0; + float volume = 0.0f; + + const idSoundShader *shader = declManager->FindSound( shaderName ); + assert( shader ); + if( !shader ) { + return; + } + + if( !GetSoundEmitter() || !GetSoundEmitter()->CurrentlyPlaying() ) { + soundLength = idSound::StartSoundShader( shader, channel, soundShaderFlags, broadcast );//Not sure if we should broadcast + + positionOffset = DeterminePositionOffset(); + if( DetermineVolume(volume) ) { + HH_SetSoundVolume( volume, channel ); + } + } + + CancelEvents( &EV_StartDelayedSoundShader ); + PostEventSec( &EV_StartDelayedSoundShader, MS2SEC(soundLength) + RandomRange(spawnArgs.GetFloat("s_minDelay"), spawnArgs.GetFloat("s_maxDelay")), shaderName, (int)channel, (int)soundShaderFlags, (int)broadcast ); +} + +void hhSound::Event_SubtitleOff( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->hud ) { + player->hud->HandleNamedEvent("RemoveSubtitleInstant"); + } +} + +/* +================ +hhSound::Event_SetTargetHandles + +Copied from hhLight +================ +*/ +void hhSound::Event_SetTargetHandles( void ) { + int i; + idEntity *targetEnt = NULL; + + if ( !refSound.referenceSound ) { + refSound.referenceSound = gameSoundWorld->AllocSoundEmitter(); + } + + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt ) { + if( targetEnt->IsType(idLight::Type) ) { + static_cast(targetEnt)->SetLightParent( this ); + } + + targetEnt->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + targetEnt->GetRenderEntity()->referenceSound = refSound.referenceSound; + + // update the renderEntity to the renderer + targetEnt->UpdateVisuals(); + } + } +} + +/* +================ +hhSound::Save +================ +*/ +void hhSound::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( positionOffset ); +} + +/* +================ +hhSound::Restore +================ +*/ +void hhSound::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( positionOffset ); +} + diff --git a/neo/prey/Prey/prey_sound.h b/neo/prey/Prey/prey_sound.h new file mode 100644 index 00000000..4440b312 --- /dev/null +++ b/neo/prey/Prey/prey_sound.h @@ -0,0 +1,38 @@ +#ifndef __HH_SOUND_H +#define __HH_SOUND_H + +extern const idEventDef EV_Sound; +extern const idEventDef EV_ResetTargetHandles; + +class hhSound : public idSound { + CLASS_PROTOTYPE( hhSound ); + + public: + hhSound(); + void Spawn(); + virtual bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int* length ); + virtual void StopSound( const s_channelType channel, bool broadcast ); + void StartDelayedSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast ); + void StopDelayedSound( const s_channelType channel, bool broadcast = false ); + float GetCurrentAmplitude(const s_channelType channel); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + float RandomRange( const float min, const float max ); + + bool DetermineVolume( float& volume ); + idVec3 DeterminePositionOffset(); + + protected: + void Event_SetTargetHandles(); + void Event_StartDelayedSoundShader( const char *shader, const s_channelType channel, int soundShaderFlags = 0, bool broadcast = false ); + void Event_SubtitleOff( void ); + protected: + idVec3 positionOffset; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_soundleadincontroller.cpp b/neo/prey/Prey/prey_soundleadincontroller.cpp new file mode 100644 index 00000000..43c012a7 --- /dev/null +++ b/neo/prey/Prey/prey_soundleadincontroller.cpp @@ -0,0 +1,264 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( idClass, hhSoundLeadInController ) +END_CLASS + +/* +================ +hhSoundLeadInController::hhSoundLeadInController + +HUMANHEAD: aob +================ +*/ +hhSoundLeadInController::hhSoundLeadInController() { + leadInShader = NULL; + loopShader = NULL; + leadOutShader = NULL; + + startTime = 0; + endTime = 0; + + owner = NULL; + + //rww - networking-related variables + lastLeadChannel = SND_CHANNEL_ANY; + lastLoopChannel = SND_CHANNEL_ANY; + bPlaying = false; + iLoopOnlyOnLocal = -1; +} + +/* +================ +hhSoundLeadInController::SetOwner + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetOwner( idEntity* ent ) { + owner = ent; +} + +/* +================ +hhSoundLeadInController::WriteToSnapshot + +HUMANHEAD: rww +================ +*/ +void hhSoundLeadInController::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(lastLeadChannel, 8); + msg.WriteBits(lastLoopChannel, 8); + + msg.WriteBits(owner.GetSpawnId(), 32); + + msg.WriteBits(bPlaying, 1); +} + +/* +================ +hhSoundLeadInController::ReadFromSnapshot + +HUMANHEAD: rww +note that the methods used here are not failsafe given the range of functionality +within this class, and this logic may need to be adjusted on a per-case basis if +more instances are to be sync'd over the net using this method. +================ +*/ +void hhSoundLeadInController::ReadFromSnapshot( const idBitMsgDelta &msg ) { + lastLeadChannel = msg.ReadBits(8); + lastLoopChannel = msg.ReadBits(8); + + owner.SetSpawnId(msg.ReadBits(32)); + + bool nowPlaying = !!msg.ReadBits(1); + if (nowPlaying != bPlaying) { + if (nowPlaying) { + StartSound(lastLeadChannel, lastLoopChannel); + } + else { + StopSound(lastLeadChannel, lastLoopChannel); + } + } +} + +/* +================ +hhSoundLeadInController::SetLeadIn + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetLeadIn( const char* soundname ) { + leadInShader = declManager->FindSound( soundname, false ); +} + +/* +================ +hhSoundLeadInController::SetLoop + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetLoop( const char* soundname ) { + loopShader = declManager->FindSound( soundname, false ); +} + +/* +================ +hhSoundLeadInController::SetLeadOut + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::SetLeadOut( const char* soundname ) { + leadOutShader = declManager->FindSound( soundname, false ); +} + +/* +================ +hhSoundLeadInController::StartSound + +HUMANHEAD: aob +================ +*/ +int hhSoundLeadInController::StartSound( const s_channelType leadChannel, const s_channelType loopChannel, int soundShaderFlags, bool broadcast ) { + int length = 0; + + if( !owner.IsValid() ) { + return 0; + } + + if( loopShader ) { + owner->StopSound( loopChannel, broadcast ); + } + + if( leadOutShader ) { + owner->StopSound( leadChannel, broadcast ); + } + + if( leadInShader ) { + owner->StartSoundShader( leadInShader, leadChannel, 0, broadcast, &length ); + StartFade( leadInShader, leadChannel, endTime, startTime, leadInShader->GetVolume(), length ); + + startTime = gameLocal.GetTime(); + endTime = startTime + length; + } + + if (iLoopOnlyOnLocal == -1 || iLoopOnlyOnLocal == gameLocal.localClientNum) { //rww - for spirit music and whatever else needs it + owner->StartSoundShader( loopShader, loopChannel, 0, broadcast, NULL ); + StartFade( loopShader, loopChannel, startTime, endTime, loopShader->GetVolume(), length ); + } + + //rww - for networking + lastLeadChannel = leadChannel; + lastLoopChannel = loopChannel; + bPlaying = true; + + return length; +} + +/* +================ +hhSoundLeadInController::StopSound + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::StopSound( const s_channelType leadChannel, const s_channelType loopChannel, bool broadcast ) { + int length = 0; + + if( !owner.IsValid() ) { + return; + } + + if( leadInShader ) { + owner->StopSound( leadChannel, broadcast ); + } + + if( loopShader ) { + owner->StopSound( loopChannel, broadcast ); + } + + if( leadOutShader ) { + owner->StartSoundShader( leadOutShader, leadChannel, 0, broadcast, &length ); + StartFade( leadOutShader, leadChannel, startTime, endTime, hhMath::Scale2dB(0.0f), length ); + + startTime = gameLocal.GetTime(); + endTime = startTime + length; + } + + //rww - for networking + lastLeadChannel = leadChannel; + lastLoopChannel = loopChannel; + bPlaying = false; +} + +/* +================ +hhSoundLeadInController::CalculateScale + +HUMANHEAD: aob +================ +*/ +float hhSoundLeadInController::CalculateScale( const float value, const float min, const float max ) { + return hhUtils::CalculateScale( value, min, max ); +} + +/* +================ +hhSoundLeadInController::StartFade + +HUMANHEAD: aob +================ +*/ +void hhSoundLeadInController::StartFade( const idSoundShader* shader, const s_channelType channel, int start, int end, int finaldBVolume, int duration ) { +/* + float scale = CalculateScale( gameLocal.GetTime(), start, end ); + + scale *= hhMath::dB2Scale( shader->GetVolume() ); + owner->HH_SetSoundVolume( scale, channel ); + owner->FadeSoundShader( finaldBVolume, duration, channel ); +*/ +} + +/* +================ +hhSoundLeadInController::Save +================ +*/ +void hhSoundLeadInController::Save( idSaveGame *savefile ) const { + savefile->WriteSoundShader( leadInShader ); + savefile->WriteSoundShader( loopShader ); + savefile->WriteSoundShader( leadOutShader ); + savefile->WriteInt( startTime ); + savefile->WriteInt( endTime ); + + owner.Save( savefile ); +} + +/* +================ +hhSoundLeadInController::Restore +================ +*/ +void hhSoundLeadInController::Restore( idRestoreGame *savefile ) { + savefile->ReadSoundShader( leadInShader ); + savefile->ReadSoundShader( loopShader ); + savefile->ReadSoundShader( leadOutShader ); + savefile->ReadInt( startTime ); + savefile->ReadInt( endTime ); + + owner.Restore( savefile ); +} + +/* +================ +hhSoundLeadInController::SetLoopOnlyOnLocal +================ +*/ +void hhSoundLeadInController::SetLoopOnlyOnLocal(int loopOnlyOnLocal) { + iLoopOnlyOnLocal = loopOnlyOnLocal; +} diff --git a/neo/prey/Prey/prey_soundleadincontroller.h b/neo/prey/Prey/prey_soundleadincontroller.h new file mode 100644 index 00000000..dcb178a3 --- /dev/null +++ b/neo/prey/Prey/prey_soundleadincontroller.h @@ -0,0 +1,53 @@ +#ifndef __HH_SOUND_LEADIN_CONTROLLER_H +#define __HH_SOUND_LEADIN_CONTROLLER_H + +class hhSoundLeadInController : public idClass { + CLASS_PROTOTYPE( hhSoundLeadInController ); + + public: + hhSoundLeadInController(); + + void SetOwner( idEntity* ent ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void SetLeadIn( const char* soundname ); + void SetLoop( const char* soundname ); + void SetLeadOut( const char* soundname ); + + int StartSound( const s_channelType leadChannel, const s_channelType loopChannel, int soundShaderFlags = 0, bool broadcast = false ); + void StopSound( const s_channelType leadChannel, const s_channelType loopChannel, bool broadcast = false ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetLoopOnlyOnLocal(int loopOnlyOnLocal); + + protected: + float CalculateScale( const float value, const float min, const float max ); + void StartFade( const idSoundShader* shader, const s_channelType channel, int start, int end, int finaldBVolume, int duration ); + + protected: + void Event_StartSoundShaderEx( idSoundShader* shader, const s_channelType channel, int soundShaderFlags, bool broadcast ); + + protected: + const idSoundShader* leadInShader; + const idSoundShader* loopShader; + const idSoundShader* leadOutShader; + + int startTime; + int endTime; + + idEntityPtr owner; + + //rww - everything below here is for networking, no need to save/restore + s_channelType lastLeadChannel; + s_channelType lastLoopChannel; + public: + bool bPlaying; + int iLoopOnlyOnLocal; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_spiritbridge.cpp b/neo/prey/Prey/prey_spiritbridge.cpp new file mode 100644 index 00000000..3198098b --- /dev/null +++ b/neo/prey/Prey/prey_spiritbridge.cpp @@ -0,0 +1,70 @@ +//************************************************************************** +//** +//** PREY_SPIRITBRIDGE.CPP +//** +//** Game code for the spirit bridge +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +CLASS_DECLARATION( idEntity, hhSpiritBridge ) + EVENT( EV_Activate, hhSpiritBridge::Event_Activate ) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSpiritBridge::Spawn +// +//========================================================================== + +void hhSpiritBridge::Spawn(void) { + fl.takedamage = false; + + if ( !spawnArgs.GetInt( "start_off" ) ) { + GetPhysics()->SetContents( CONTENTS_SPIRITBRIDGE ); + } else { // Start the bridge off + GetPhysics()->SetContents( 0 ); + Hide(); + } + + SetShaderParm( SHADERPARM_MODE, gameLocal.random.CRandomFloat() ); +} + +//========================================================================== +// +// hhSpiritBridge::Event_Activate +// +//========================================================================== + +void hhSpiritBridge::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + GetPhysics()->SetContents( CONTENTS_SPIRITBRIDGE ); + } else { + Hide(); + GetPhysics()->SetContents( 0 ); + } +} diff --git a/neo/prey/Prey/prey_spiritbridge.h b/neo/prey/Prey/prey_spiritbridge.h new file mode 100644 index 00000000..dab79dbe --- /dev/null +++ b/neo/prey/Prey/prey_spiritbridge.h @@ -0,0 +1,17 @@ + +#ifndef __PREY_SPIRITBRIDGE_H__ +#define __PREY_SPIRITBRIDGE_H__ + +//class hhPlayer; + +class hhSpiritBridge : public idEntity { +public: + CLASS_PROTOTYPE( hhSpiritBridge ); + + void Spawn( void ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +#endif /* __PREY_SPIRITBRIDGE_H__ */ diff --git a/neo/prey/Prey/prey_spiritproxy.cpp b/neo/prey/Prey/prey_spiritproxy.cpp new file mode 100644 index 00000000..4945dd78 --- /dev/null +++ b/neo/prey/Prey/prey_spiritproxy.cpp @@ -0,0 +1,1094 @@ +//************************************************************************** +//** +//** PREY_SPIRITPROXY.CPP +//** +//** Game code for the player proxy dropped when the player spirit walks +//** +//************************************************************************** + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define MIN_ACTIVATION_TIME SEC2MS( 1.0f ) + +const idEventDef EV_SpawnEffect( "", NULL ); +const idEventDef EV_OrientToGravity( "orientToGravity", "d" ); + +//========================================================================== +// hhSpiritProxy +//========================================================================== + +CLASS_DECLARATION( idActor, hhSpiritProxy ) + EVENT( EV_SpawnEffect, hhSpiritProxy::Event_SpawnEffect ) + EVENT( EV_OrientToGravity, hhSpiritProxy::Event_OrientToGravity ) + EVENT( EV_ResetGravity, hhSpiritProxy::Event_ResetGravity ) + EVENT( EV_ShouldRemainAlignedToAxial, hhSpiritProxy::Event_ShouldRemainAlignedToAxial ) + EVENT( EV_Broadcast_AssignFx, hhSpiritProxy::Event_AssignSpiritFx ) +END_CLASS + +hhSpiritProxy::hhSpiritProxy() { + fl.networkSync = true; + playerModelNum = 0; +} + +void hhSpiritProxy::Spawn(void) { + fl.takedamage = true; + spiritFx = NULL; + + clientAnimated = false; + netAnimType = 0; + + if (gameLocal.isMultiplayer) { + SetModel("model_multiplayer_tommy"); + SetSkinByName("skins/characters/tommy_mp_spirit"); + playerModelNum = 0; + } + + // Required so that models move in place. + GetAnimator()->RemoveOriginOffset( true ); + + if (gameLocal.isMultiplayer && !IsType(hhDeathProxy::Type)) { //rww - ambient sound for mp + StartSound( "snd_spiritSound", SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +//========================================================================== +// +// hhSpiritProxy::SetModel +// +//========================================================================== +void hhSpiritProxy::SetModel( const char *modelname ) { + spawnArgs.Set("playerModel", modelname); + idActor::SetModel(modelname); +} + +//========================================================================== +// +// hhSpiritProxy::UpdateModelForPlayer +// +//========================================================================== +void hhSpiritProxy::UpdateModelForPlayer(void) { + int modelNum = 0; + player->GetUserInfo()->GetInt("ui_modelNum", "0", modelNum); + + if (modelNum != playerModelNum) { //time to change models then. + idStr customModel; + if (!IsType(hhMPDeathProxy::Type)) { //don't do this for death prox, since it uses the spirit fadeaway skin + SetSkin(NULL); //destroy custom skin on the spirit proxy so that the appropriate player skin is used. + } + playerModelNum = modelNum; + if (player->spawnArgs.GetString(va("model_mp%i", modelNum), "", customModel)) { + SetModel(customModel.c_str()); + } + else { + SetModel("model_multiplayer_tommy"); + } + if (!IsType(hhMPDeathProxy::Type)) { + //new: check for a custom spirit proxy skin for the given model + if (!player->spawnArgs.GetString(va("skin_mpspirit%i", modelNum), "", customModel)) { + customModel = "skins/characters/tommy_mp_spirit"; + } + SetSkinByName(customModel); + + StartAnimation(); //restart animation on new model (unless you're a corpse) + } + } +} + +//========================================================================== +// +// hhSpiritProxy::Think +// +//========================================================================== +void hhSpiritProxy::Think() { + idVec3 oldOrigin = physicsObj.GetOrigin(); + idVec3 oldVelocity = physicsObj.GetLinearVelocity(); + + if (gameLocal.isMultiplayer) { + DrawPlayerIcons(); + + if (player.IsValid() && player.GetEntity() && player->IsType(hhPlayer::Type) && !IsType(hhMPDeathProxy::Type)) { + UpdateModelForPlayer(); + } + } + idActor::Think(); + + if (player.IsValid() && player->IsType(hhPlayer::Type) && (!IsType(hhMPDeathProxy::Type) || !IsType(hhMPDeathProxy::Type))) { + CrashLand(oldOrigin, oldVelocity); + } +} + +//========================================================================== +// +// hhSpiritProxy::CreateProxy +// +// Spawns a proxy object and then activates it +//========================================================================== + +hhSpiritProxy *hhSpiritProxy::CreateProxy( const char *name, hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy *proxy; + + proxy = (hhSpiritProxy *)gameLocal.SpawnObject( name ); + if( !proxy ) { + gameLocal.Error("hhSpiritProxy::CreateProxy: Could not spawn the player proxy\n"); + } + + proxy->ActivateProxy( owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis ); + + return proxy; +} + + +//========================================================================== +// +// hhSpiritProxy::Event_SpawnEffect +// +//========================================================================== + +void hhSpiritProxy::Event_SpawnEffect() { + hhFxInfo fxInfo; + idVec3 boneOffset; + idMat3 boneAxis; + + GetJointWorldTransform( spawnArgs.GetString( "bone_spiritFx" ), boneOffset, boneAxis ); + + fxInfo.RemoveWhenDone( false ); + fxInfo.SetNormal( boneAxis[1] ); + fxInfo.SetEntity( this ); + BroadcastFxInfo( spawnArgs.GetString( "fx_spirit" ), boneOffset, GetAxis(), &fxInfo, &EV_Broadcast_AssignFx ); +} + +//========================================================================== +// +// hhSpiritProxy::ActivateProxy +// +//========================================================================== + +void hhSpiritProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + assert( owner ); + + player = owner; + + viewAxis = newViewAxis; + viewAngles = newViewAngles; + eyeAxis = newEyeAxis; + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(owner->GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.SetOrigin( origin ); + physicsObj.SetAxis( bboxAxis ); + physicsObj.SetClipMask( CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_FORCEFIELD ); // HUMANHEAD mdl: MASK_PLAYERSOLID - CONTENTS_BODY + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP | CONTENTS_RENDERMODEL ); + physicsObj.CheckWallWalk( true ); + if( player->IsCrouching() ) { + physicsObj.ForceCrouching(); + } + SetPhysics( &physicsObj ); + + if (owner->GetPhysics()->IsType(hhPhysics_Player::Type)) { //rww - don't stay half-oriented if player goes into spirit while gravity-flipping + hhPhysics_Player *plPhys = static_cast(owner->GetPhysics()); + OrientToGravity(plPhys->OrientToGravity()); + SetGravity(plPhys->GetGravity()); + } + + // Save the time when activated, to keep the player from being bounced back into the proxy too quickly + activationTime = gameLocal.time; + + StartAnimation(); + + UpdateVisuals(); + + // Set eye height + eyeOffset = GetPhysics()->GetBounds()[ 1 ].z - 6; + + // TODO: AIMSG_REMOVE: Tell the AI that spirit walk has started + + // Spawn spirit effect + PostEventMS( &EV_SpawnEffect, spawnArgs.GetInt( "spiritBlendTime", "400" ) ); + + if (!IsType(hhDeathProxy::Type) && !IsType(hhDeathWalkProxy::Type)) { + idVec3 vel = player->GetPhysics()->GetLinearVelocity(); + GetPhysics()->SetLinearVelocity( player->GetPhysics()->GetLinearVelocity() ); + GetPhysics()->SetAngularVelocity( player->GetPhysics()->GetAngularVelocity() ); + } + + // Make sure spiritproxy is affected by wallwalkmovers + BecomeActive( TH_PHYSICS ); +} + +//========================================================================== +// +// hhSpiritProxy::DeactivateProxy +// +//========================================================================== + +void hhSpiritProxy::DeactivateProxy(void) { + if( !player.IsValid() ) { + return; + } + + // TODO: AIMSG_REMOVED Tell the AI that spirit walk has ended + + // TODO: AIMSG_REMOVED Tell monsters they 'heard' a sound where the player spirit was removed from + // This makes it so monsters will 'investigate' where a player just disappeared from. Could be cool game dynamic? Maybe not needed? + + if (!IsType(hhDeathProxy::Type)) { //rww - for deathwalk, the player will manage values + RestorePlayerLocation( GetOrigin(), GetPhysics()->GetAxis(), viewAxis[0], viewAngles ); + idVec3 vel = GetPhysics()->GetLinearVelocity(); + player->GetPhysics()->SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + player->GetPhysics()->SetAngularVelocity( GetPhysics()->GetAngularVelocity() ); + } + + //HUMANHEAD rww - in multiplayer, telefrag other players who are in my body + if (gameLocal.isMultiplayer && !gameLocal.isClient) { + idBounds testBounds = player->GetPhysics()->GetAbsBounds(); + if (testBounds != bounds_zero) { + idEntity *touch[ MAX_GENTITIES ]; + + testBounds.ExpandSelf(-4.0f); //don't do anything if they are right on the edge, to avoid exploiting this by going up to someone and spiriting to kill them + int num = gameLocal.clip.EntitiesTouchingBounds(testBounds, player->GetPhysics()->GetClipMask(), touch, MAX_GENTITIES); + for (int i = 0; i < num; i++) { + if (touch[i] && touch[i]->IsType(hhPlayer::Type) && touch[i] != player.GetEntity() && touch[i]->fl.takedamage) { + touch[i]->Damage(player.GetEntity(), player.GetEntity(), vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT); + } + } + } + } + //HUMANHEAD END + + Hide(); // JRM: Hide this so it doesn't stick around while waiting to be removed + GetPhysics()->SetContents( 0 ); + PostEventMS( &EV_Remove, 1000 ); // Keep the proxy around for a few frames to deal with removal issues - JRM: made bigger + player = NULL; // Completely disconnect the proxy from the owner + + if ( spiritFx.IsValid() ) { // Remove spirit effect + spiritFx->Nozzle( false ); + SAFE_REMOVE( spiritFx ); + } + + fl.refreshReactions = FALSE; + CancelEvents( &EV_SpawnEffect ); +} + +//========================================================================== +// +// hhSpiritProxy::RestorePlayerLocation +// +//========================================================================== +void hhSpiritProxy::RestorePlayerLocation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ) { + if( IsCrouching() ) { + player->ForceCrouching(); + } + + player->TeleportNoKillBox( origin, bboxAxis, viewDir, (angles.ToMat3() * eyeAxis.Transpose()).ToAngles() ); +} + +//========================================================================== +// +// hhSpiritProxy::StartAnimation +// +// Starts the animation on the proxy +//========================================================================== +void hhSpiritProxy::StartAnimation() { + int spiritBlendTime = spawnArgs.GetInt( "spiritBlendTime", "400" ); + // Copy the current player animation to blend into the spirit anim + GetAnimator()->CopyAnimations( *( player->GetAnimator() ) ); + + // Play the initial animation + int anim; + + if (gameLocal.isClient) { //rww + switch (netAnimType) { + case 1: + anim = GetAnimator()->GetAnim("spiritleave"); + break; + case 2: + anim = GetAnimator()->GetAnim("crouch"); + break; + default: + assert(!"hhSpiritProxy has invalid netAnimType"); + anim = GetAnimator()->GetAnim("spiritleave"); + break; + } + } + else { + if ( IsCrouching() ) { + // Determine contents of the bounds if the player is crouching + idBounds bounds; + idTraceModel trm; + + 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() ); + + trm.SetupBox( bounds ); + idClipModel *clipModel = new idClipModel(trm); + int contents = gameLocal.clip.Contents( player->GetOrigin(), clipModel, player->GetAxis(), CONTENTS_SOLID, player.GetEntity() ); + delete clipModel; + + if ( contents & CONTENTS_SOLID ) { // Standing upright collides with geometry, so play the crouch anim when spiritwalking + anim = GetAnimator()->GetAnim("crouch"); + netAnimType = 2; + } else { + anim = GetAnimator()->GetAnim("spiritleave"); + netAnimType = 1; + } + } else { + anim = GetAnimator()->GetAnim("spiritleave"); + netAnimType = 1; + } + } + GetAnimator()->ClearAllAnims( gameLocal.time, spiritBlendTime ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, spiritBlendTime ); +} + +//========================================================================== +// +// hhSpiritProxy::Damage +// +// When damaged, the proxy forces the player to automatically return +//========================================================================== + +void hhSpiritProxy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + + if( !player.IsValid() || !player->IsSpiritOrDeathwalking() ) { + return; + } + if (gameLocal.isClient) { //rww - don't predict damage on the spirit proxy + return; + } + + if (!gameLocal.isMultiplayer) { //rww - not in mp + // Don't allow the player to be bounced back into the proxy immediately if damaged + if ( gameLocal.time - activationTime < MIN_ACTIVATION_TIME ) { + return; + } + } + + // Save the player, since it is NULLed on this proxy when removed from spiritwalk + hhPlayer *currentPlayer = player.GetEntity(); + + // Disable spiritwalk + // rww - let's defer this to the next frame so other projectiles and things that would impact this frame still hit + if (idEvent::NumQueuedEvents(currentPlayer, &EV_StopSpiritWalk) <= 0) { + currentPlayer->PostEventMS(&EV_StopSpiritWalk, 0); + } + //currentPlayer->StopSpiritWalk(); + + if ( attacker && attacker == player.GetEntity() ) { // If the attacker is the player itself, then just snap back from spirit mode, without doing any damage + return; + } + + // Apply the damage to the player itself + // rww - post this as an event too so we don't take the damage until after we're out of spirit form + float localDmgScale = damageScale; + if (gameLocal.isMultiplayer) { //rww - scale up the damage against bodies in mp, to increase punishment when your body is found + localDmgScale *= 2.0f; + } + currentPlayer->PostEventMS(&EV_DamagePlayer, 1, inflictor, attacker, dir, damageDefName, localDmgScale, location); + //currentPlayer->Damage( inflictor, attacker, dir, damageDefName, localDmgScale, location ); +} + +/* +=============== +hhSpiritProxy::OrientToGravity +=============== +*/ +void hhSpiritProxy::OrientToGravity( bool orient ) { + physicsObj.OrientToGravity( orient ); +} + +/* +=============== +hhSpiritProxy::Event_OrientToGravity +=============== +*/ +void hhSpiritProxy::Event_OrientToGravity( bool orient ) { + OrientToGravity( orient ); +} + +/* +=============== +hhSpiritProxy::Event_AssignSpiritFx +=============== +*/ +void hhSpiritProxy::Event_AssignSpiritFx( hhEntityFx* fx ) { + spiritFx = fx; +} + +/* +=============== +hhSpiritProxy::Event_ResetGravity + +HUMANHEAD: pdm: Posted when entity is leaving a gravity zone +=============== +*/ +void hhSpiritProxy::Event_ResetGravity() { + if( IsWallWalking() ) { + return; // Don't reset if wallwalking + } + + idActor::Event_ResetGravity(); + + OrientToGravity( true ); // let it reset orientation +} + +//========================================================================== +// +// hhSpiritProxy::ShouldRemainAlignedToAxial +// +//========================================================================== +void hhSpiritProxy::ShouldRemainAlignedToAxial( bool remainAligned ) {//HUMANHEAD + physicsObj.ShouldRemainAlignedToAxial( remainAligned ); +} + +//========================================================================== +// +// hhSpiritProxy::Event_ShouldRemainAlignedToAxial +// +//========================================================================== +void hhSpiritProxy::Event_ShouldRemainAlignedToAxial( bool remainAligned ) { + ShouldRemainAlignedToAxial( remainAligned ); +} + +/* +================ +hhSpiritProxy::UpdateModelTransform +mdl: Based on hhPlayer::UpdateModelTransform +================ +*/ +void hhSpiritProxy::UpdateModelTransform( void ) { + if( af.IsActive() ) { + return idActor::UpdateModelTransform(); + } + + idVec3 origin; + idMat3 axis; + + if( GetPhysicsToVisualTransform(origin, axis) ) { + GetRenderEntity()->axis = axis; + GetRenderEntity()->origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + GetRenderEntity()->axis = GetAxis(); + GetRenderEntity()->origin = GetOrigin(); + } +} + +bool hhSpiritProxy::AllowCollision(const trace_t &collision) { + if (collision.fraction < 1.0f && collision.c.entityNum < MAX_CLIENTS && collision.c.entityNum >= 0 && gameLocal.entities[collision.c.entityNum]) { + if (player.GetEntity() == gameLocal.entities[collision.c.entityNum]) { + return false; //do not collide with the owner of this spirit proxy + } + } + return true; +} + +void hhSpiritProxy::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + + player.Save( savefile ); + + savefile->WriteAngles( viewAngles ); + savefile->WriteMat3( eyeAxis ); + + spiritFx.Save( savefile ); + + savefile->WriteInt( cachedCurrentWeapon ); + savefile->WriteInt( activationTime ); +} + +void hhSpiritProxy::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + player.Restore( savefile ); + + savefile->ReadAngles( viewAngles ); + savefile->ReadMat3( eyeAxis ); + + spiritFx.Restore( savefile ); + + savefile->ReadInt( cachedCurrentWeapon ); + savefile->ReadInt( activationTime ); +} + +#define _CHEAP_PROX_SYNC + +void hhSpiritProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { +#ifdef _CHEAP_PROX_SYNC + //we don't want to deal with physics stuff for this proxy on the client i suppose. + //write the origin/axis directly and don't worry about varying physics types. + const idVec3 &origin = renderEntity.origin; + msg.WriteFloat(origin.x); + msg.WriteFloat(origin.y); + msg.WriteFloat(origin.z); +#endif + + idQuat q = renderEntity.axis.ToQuat(); + msg.WriteFloat(q.w); + msg.WriteFloat(q.x); + msg.WriteFloat(q.y); + msg.WriteFloat(q.z); + + msg.WriteBits(fl.hidden, 1); + + msg.WriteBits(player.GetSpawnId(), 32); + + msg.WriteBits(netAnimType, 2); + +#ifndef _CHEAP_PROX_SYNC + msg.WriteBits(physicsObj.GetClipMask(), 32); + msg.WriteBits(physicsObj.GetContents(), 32); + physicsObj.WriteToSnapshot(msg, false); +#endif +} + +void hhSpiritProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { +#ifdef _CHEAP_PROX_SYNC + idVec3 origin; + origin.x = msg.ReadFloat(); + origin.y = msg.ReadFloat(); + origin.z = msg.ReadFloat(); +#endif + + idQuat q; + q.w = msg.ReadFloat(); + q.x = msg.ReadFloat(); + q.y = msg.ReadFloat(); + q.z = msg.ReadFloat(); + +#ifdef _CHEAP_PROX_SYNC + GetPhysics()->SetOrigin(origin); + + idMat3 axis = q.ToMat3(); + GetPhysics()->SetAxis(axis); + viewAxis = axis; +#else + viewAxis = q.ToMat3(); +#endif + + bool hidden = !!msg.ReadBits(1); + if (hidden != fl.hidden) { + if (hidden) { + Hide(); + } + else { + Show(); + } + } + + player.SetSpawnId(msg.ReadBits(32)); + + netAnimType = msg.ReadBits(2); + + //if we haven't started the animation on the client, then start it + if (!clientAnimated && netAnimType && player.IsValid() && player.GetEntity() && player->IsType(hhPlayer::Type) && player->GetAnimator()) { //verify the owner is still valid + StartAnimation(); + clientAnimated = true; + } + +#ifndef _CHEAP_PROX_SYNC + if (player.IsValid() && GetPhysics() != &physicsObj) { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(player->GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.CheckWallWalk( true ); + if( player->IsCrouching() ) { + physicsObj.ForceCrouching(); + } + SetPhysics( &physicsObj ); + } + physicsObj.SetClipMask(msg.ReadBits(32)); + physicsObj.SetContents(msg.ReadBits(32)); + physicsObj.ReadFromSnapshot(msg, false); +#endif +} + +void hhSpiritProxy::ClientPredictionThink( void ) { + Think(); +} + +//proxy needs to clear its icons as well when exiting the snapshot +void hhSpiritProxy::NetZombify(void) { + HidePlayerIcons(); + idActor::NetZombify(); +} + +// Derived from hhMonsterAI::CrashLand() and hhPlayer::CrashLand() -mdl +void hhSpiritProxy::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + const trace_t& trace = physicsObj.GetGroundTrace(); + if ( af.IsActive() || (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) { + return; + } + + //aob - only check when we land on the ground + //If we get here we can assume we currently have ground contacts + if( physicsObj.HadGroundContacts() ) { + return; + } + + // if the monster wasn't going down + if ( ( oldVelocity * -physicsObj.GetGravityNormal() ) >= 0.0f ) { + return; + } + + waterLevel_t waterLevel = physicsObj.GetWaterLevel(); + + // never take falling damage if completely underwater + if ( waterLevel == WATERLEVEL_HEAD ) { + return; + } + + // no falling damage if touching a nodamage surface + bool 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; + } + } + + idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace ); + float delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * physicsObj.GetGravityNormal(); + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < player->crashlandSpeed_jump ) { + return; // Early out + } + + if( trace.fraction == 1.0f ) { + return; + } + + // Determine damage to what you're landing on + idVec3 fallDir = oldVelocity; + fallDir.Normalize(); + float damageScale = hhUtils::CalculateScale( delta, player->crashlandSpeed_soft, player->crashlandSpeed_fatal ); + idVec3 reverseContactNormal = -physicsObj.GetGroundContactNormal(); + idEntity *entity = gameLocal.GetTraceEntity( trace ); + if( entity && trace.c.entityNum != ENTITYNUM_WORLD ) { + entity->ApplyImpulse( this, 0, trace.c.point, (oldVelocity * reverseContactNormal) * reverseContactNormal );//Not sure if this impulse is large enough + + const char* entityDamageName = spawnArgs.GetString( "def_damageFellOnto" ); + if( *entityDamageName && damageScale > 0.0f) { + entity->Damage( this, this, fallDir, entityDamageName, damageScale, INVALID_JOINT ); + } + } + + // Calculate damage to self + const char* selfDamageName = NULL; + if ( delta < player->crashlandSpeed_soft ) { // Soft Fall + //AI_SOFTLANDING = true; + selfDamageName = player->spawnArgs.GetString( "def_damageSoftFall" ); + } + else if ( delta < player->crashlandSpeed_fatal ) { // Hard Fall + //AI_HARDLANDING = true; + selfDamageName = player->spawnArgs.GetString( "def_damageHardFall" ); + } + else { // Fatal Fall + //AI_HARDLANDING = true; + selfDamageName = player->spawnArgs.GetString( "def_damageFatalFall" ); + } + + if( *selfDamageName && damageScale > 0.0f && !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + hhPlayer *tmp = player.GetEntity(); + player->StopSpiritWalk(true); // Invalidates player + HH_ASSERT(!player.IsValid()); // If it's still valid, we failed to stop spirit walking + tmp->Damage( NULL, NULL, fallDir, selfDamageName, damageScale, INVALID_JOINT ); + } +} + +//========================================================================== +// hhDeathWalkProxy +//========================================================================== + +CLASS_DECLARATION( hhSpiritProxy, hhDeathWalkProxy ) +END_CLASS + +hhDeathProxy::~hhDeathProxy(void) { + HH_ASSERT( gameLocal.isMultiplayer || !player.IsValid() || player->IsDeathWalking() ); +} + +/* +================ +hhDeathWalkProxy::Spawn +================ +*/ +void hhDeathWalkProxy::Spawn() { + initialPos = GetOrigin(); + spawnArgs.GetFloat("bodyMoveScale", "512", bodyMoveScale); + fl.takedamage = false; + timeSinceStage2Started = 0; +} + +void hhDeathWalkProxy::Save(idSaveGame *savefile) const { + savefile->WriteVec3(initialPos); + savefile->WriteFloat(bodyMoveScale); + savefile->WriteInt(timeSinceStage2Started); +} + +void hhDeathWalkProxy::Restore(idRestoreGame *savefile) { + savefile->ReadVec3(initialPos); + savefile->ReadFloat(bodyMoveScale); + savefile->ReadInt(timeSinceStage2Started); +} + +void hhDeathWalkProxy::Think() { + float lerpTime = 0.05f; + + if (!player.IsValid() || !player.GetEntity() || !player->IsType(hhPlayer::Type) || !player->IsDeathWalking()) { + //if player has become invalid or is no longer deathwalking, i wish to be removed. + PostEventMS(&EV_Remove, 0); + return; + } + + hhSpiritProxy::Think(); + + float spiritPercent = player->GetDeathWalkPower() / player->spawnArgs.GetFloat( "deathWalkPowerMax", "1000" ); + + idVec3 newPos = initialPos; + if (player->DeathWalkStage2()) { + timeSinceStage2Started += gameLocal.msec; + if (timeSinceStage2Started > player->spawnArgs.GetInt("deathwalkBodyDropDelayMS")) { + newPos.z = initialPos.z - player->spawnArgs.GetFloat("deathwalkOffsetBelowPortal"); + lerpTime = 0.02f; + } + } + else { + timeSinceStage2Started = 0; + newPos.z = initialPos.z + bodyMoveScale - spiritPercent*bodyMoveScale; + } + + //lerp into that position + idVec3 lerped; + lerped.SLerp(GetOrigin(), newPos, lerpTime ); + SetOrigin(lerped); +} + +void hhDeathWalkProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy::ActivateProxy(owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis); + + physicsObj.SetContents(0); //not solid + physicsObj.SetGravity(idVec3(0, 0, 0)); //no gravity + + SetInitialPos(origin); //this is our initial position to move vertically from +} + +void hhDeathWalkProxy::SetInitialPos(const idVec3 &pos) { + initialPos = pos; +} + +void hhDeathWalkProxy::StartAnimation() { + int spiritBlendTime = spawnArgs.GetInt( "spiritBlendTime", "400" ); + // Copy the current player animation to blend into the spirit anim + GetAnimator()->CopyAnimations( *( player->GetAnimator() ) ); + + // Play the initial animation + int anim = GetAnimator()->GetAnim("deathfloat"); + GetAnimator()->ClearAllAnims( gameLocal.time, spiritBlendTime ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, spiritBlendTime ); + netAnimType = 3; //rww +} + + + +//========================================================================== +// hhDeathProxy +//========================================================================== + +CLASS_DECLARATION( hhSpiritProxy, hhDeathProxy ) + EVENT( EV_SpawnEffect, hhDeathProxy::Event_SpawnEffect ) + EVENT( EV_Activate, hhDeathProxy::Event_Activate ) +END_CLASS + + +void hhDeathProxy::Spawn() { + lastPhysicalLocation.Zero(); + lastPhysicalAxis.Identity(); +} + +void hhDeathProxy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + //idActor::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +void hhDeathProxy::Event_SpawnEffect() { +} + +void hhDeathProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy::ActivateProxy( owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis ); + + // Save "return to" location + lastPhysicalLocation = origin; + lastPhysicalAxis = bboxAxis; + + // Ragdoll the proxy + StartRagdoll(); +} + +void hhDeathProxy::StartAnimation() { + if (gameLocal.isMultiplayer) { //rww + UpdateModelForPlayer(); //update proxy model for whatever the player is using. + } + // Set the start of the death proxy to the current animation in the player + GetAnimator()->CopyAnimations( *( player->GetAnimator() ) ); + GetAnimator()->CopyPoses( *( player->GetAnimator() ) ); + netAnimType = 3; //rww +} + +// mdl: Handy for debugging the death proxy +void hhDeathProxy::Event_Activate() { + hhPlayer *player = reinterpret_cast (gameLocal.GetLocalPlayer()); + ActivateProxy(player, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, player->GetEyeAxis()); +} + + + +//========================================================================== +// hhMPDeathProxy +//========================================================================== + +idCVar g_mpPlayerRagdollLife( "g_mpPlayerRagdollLife", "1500", CVAR_GAME | CVAR_INTEGER, "player ragdoll life" ); +idCVar g_mpSyncPlayerRagdoll( "g_mpSyncPlayerRagdoll", "0", CVAR_GAME | CVAR_BOOL, "whether to sync mp ragdolls" ); + +const idEventDef EV_CorpseRemove( "", NULL ); + +CLASS_DECLARATION( hhDeathProxy, hhMPDeathProxy ) + EVENT( EV_CorpseRemove, hhMPDeathProxy::Event_CorpseRemove ) +END_CLASS + + +/* +================ +hhMPDeathProxy::Spawn +================ +*/ +void hhMPDeathProxy::Spawn() { + hasInitial = false; + didFling = false; + + int delay = g_mpPlayerRagdollLife.GetInteger(); + fl.clientEvents = true; + PostEventMS(&EV_CorpseRemove, delay); + + SetShaderParm(SHADERPARM_TIME_OF_DEATH, MS2SEC(gameLocal.time+1000)); + SetSkinByName(spawnArgs.GetString("skin_death")); + //GetPhysics()->SetGravity(idVec3(0,0,512)); cheesy spirit floating upward effect (doesn't work inside grav zones) +} + +/* +================ +hhMPDeathProxy::ActivateProxy +================ +*/ +void hhMPDeathProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhDeathProxy::ActivateProxy(owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis); + + hasInitial = true; + initialPos = origin; + initialRot = bboxAxis.ToCQuat(); + + GetPhysics()->SetOrigin(initialPos); + GetPhysics()->SetAxis(initialRot.ToMat3()); +} + +/* +================ +hhMPDeathProxy::Event_CorpseRemove +================ +*/ +void hhMPDeathProxy::Event_CorpseRemove(void) { + Hide(); + GetPhysics()->SetContents(0); + if (!gameLocal.isClient) { + PostEventMS(&EV_Remove, 1000); + } +} + +/* +================ +hhMPDeathProxy::ActivateProxy +================ +*/ +void hhMPDeathProxy::SetFling(const idVec3 &point, const idVec3 &force) { + //initialFlingPoint = point; + initialFlingForce = force; +} + +/* +================ +hhMPDeathProxy::WriteToSnapshot +================ +*/ +void hhMPDeathProxy::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(player.GetSpawnId(), 32); + + msg.WriteBits(IsActiveAF(), 1); + + bool physSync = g_mpSyncPlayerRagdoll.GetBool(); //the server can decide to do this. + msg.WriteBits(physSync, 1); + if (physSync) { + GetPhysics()->WriteToSnapshot(msg); + } + else { //if we are not properly sync'ing then use just an initial orientation + msg.WriteFloat(initialPos.x); + msg.WriteFloat(initialPos.y); + msg.WriteFloat(initialPos.z); + + msg.WriteFloat(initialRot.x); + msg.WriteFloat(initialRot.y); + msg.WriteFloat(initialRot.z); + + //FIXME get some of this from the server? may be necessary if a more complex method of determining the fling direction is devised. + /* + msg.WriteFloat(initialFlingPoint.x); + msg.WriteFloat(initialFlingPoint.y); + msg.WriteFloat(initialFlingPoint.z); + */ + + msg.WriteFloat(initialFlingForce.x); + msg.WriteFloat(initialFlingForce.y); + msg.WriteFloat(initialFlingForce.z); + } +} + +/* +================ +hhMPDeathProxy::ReadFromSnapshot +================ +*/ +void hhMPDeathProxy::ReadFromSnapshot( const idBitMsgDelta &msg ) { + player.SetSpawnId(msg.ReadBits(32)); + + bool isRagging = !!msg.ReadBits(1); + + bool physSync = !!msg.ReadBits(1); + + if (physSync) { + if (isRagging && !IsActiveAF()) { //then start ragging on the client + if (player.IsValid()) { //update model for player before initiating ragdoll + UpdateModelForPlayer(); + } + StartRagdoll(); + } + GetPhysics()->ReadFromSnapshot(msg); + } + else { //if we are not properly sync'ing then use just an initial orientation + initialPos.x = msg.ReadFloat(); + initialPos.y = msg.ReadFloat(); + initialPos.z = msg.ReadFloat(); + + initialRot.x = msg.ReadFloat(); + initialRot.y = msg.ReadFloat(); + initialRot.z = msg.ReadFloat(); + + initialFlingForce.x = msg.ReadFloat(); + initialFlingForce.y = msg.ReadFloat(); + initialFlingForce.z = msg.ReadFloat(); + + if (!hasInitial) { //if not set at all yet, put it here. + GetPhysics()->SetOrigin(initialPos); //set before we start to ragdoll, so that the af starts out at a valid orientation + GetPhysics()->SetAxis(initialRot.ToMat3()); + + hasInitial = true; + } + + if (isRagging && !IsActiveAF()) { //then start ragging on the client + if (player.IsValid()) { //update model for player before initiating ragdoll + UpdateModelForPlayer(); + } + StartRagdoll(); + + //set the initial orientation again after beginning ragdoll + GetPhysics()->SetOrigin(initialPos); + GetPhysics()->SetAxis(initialRot.ToMat3()); + } + } +} + +/* +================ +hhMPDeathProxy::ClientPredictionThink +================ +*/ +void hhMPDeathProxy::ClientPredictionThink( void ) { + if (!gameLocal.isNewFrame) { + return; + } + + Think(); + if (hasInitial && !didFling) { + GetPhysics()->AddForce(0, GetPhysics()->GetOrigin(0), initialFlingForce*256.0f*256.0f); + didFling = true; + } +} + + +//========================================================================== +// +// hhPossessedProxy +// +// A version of the spirit proxy spawned when the player is possessed +//========================================================================== + +CLASS_DECLARATION( hhSpiritProxy, hhPossessedProxy ) +END_CLASS + +void hhPossessedProxy::ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) { + hhSpiritProxy::ActivateProxy(owner, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis); + + Hide(); + physicsObj.SetContents( 0 ); +} + +/* +=============== +hhSpiritProxy::DrawPlayerIcons +=============== +*/ +void hhSpiritProxy::DrawPlayerIcons( void ) { + if ( !NeedsIcon() ) { + //playerIcon.FreeIcon(); + playerTeamIcon.FreeIcon(); + return; + } + //player->UpdatePlayerIcons(); //update the owner's icon status + //playerIcon.Draw( this, INVALID_JOINT ); + playerTeamIcon.Draw( this, INVALID_JOINT ); +} + +/* +=============== +hhSpiritProxy::HidePlayerIcons +=============== +*/ +void hhSpiritProxy::HidePlayerIcons( void ) { + //playerIcon.FreeIcon(); + playerTeamIcon.FreeIcon(); +} + +/* +=============== +hhSpiritProxy::NeedsIcon +============== +*/ +bool hhSpiritProxy::NeedsIcon( void ) { + if (IsType(hhMPDeathProxy::Type)) { + return false; + } + if (!player.IsValid()) { + return false; + } + if (IsHidden()) { + return false; + } + return player->entityNumber != gameLocal.localClientNum && ( /*player->isLagged || player->isChatting ||*/ gameLocal.gameType == GAME_TDM ) && gameLocal.EntInClientSnapshot(entityNumber); +} diff --git a/neo/prey/Prey/prey_spiritproxy.h b/neo/prey/Prey/prey_spiritproxy.h new file mode 100644 index 00000000..919c859f --- /dev/null +++ b/neo/prey/Prey/prey_spiritproxy.h @@ -0,0 +1,200 @@ + +#ifndef __PREY_SPIRITPROXY_H__ +#define __PREY_SPIRITPROXY_H__ + +// nla - Forward declare +class hhPlayer; + +extern const idEventDef EV_OrientToGravity; + +// SPIRIT PROXY =============================================================== + +class hhSpiritProxy : public idActor { +public: + CLASS_PROTOTYPE( hhSpiritProxy ); + hhSpiritProxy( void ); + void Spawn( void ); + static hhSpiritProxy *CreateProxy( const char *name, hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + + virtual void SetModel( const char *modelname ); //rww + + virtual void UpdateModelForPlayer(void); //rww + virtual void Think(); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + virtual void DeactivateProxy( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool IsWallWalking() const; + virtual bool IsCrouching() const; + virtual bool ShouldRemainAlignedToAxial(); + virtual void RestorePlayerLocation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ); + virtual void StartAnimation(); + hhPlayer* GetPlayer(void) const { return player.GetEntity(); } // JRM + void OrientToGravity( bool orient ); + virtual void ShouldRemainAlignedToAxial( bool remainAligned ); + virtual bool ShouldRemainAlignedToAxial() const { return physicsObj.ShouldRemainAlignedToAxial(); } + virtual void UpdateModelTransform( void ); // mdl + virtual bool AllowCollision(const trace_t &collision); //rww + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network code + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + virtual void NetZombify(void); + + //rww - for mp player icons transferring to the prox + void DrawPlayerIcons( void ); + void HidePlayerIcons( void ); + bool NeedsIcon( void ); + + ID_INLINE float GetActivationTime( void ) const { return activationTime; } + +public: + //Overridden methods + virtual void SetAxis( const idMat3& axis ) { idEntity::SetAxis( axis ); } + + void Event_SpawnEffect(); + void Event_OrientToGravity( bool orient ); + void Event_ResetGravity(); + void Event_ShouldRemainAlignedToAxial( bool remainAligned ); + void Event_AssignSpiritFx( hhEntityFx* fx ); + +protected: + void CrashLand(const idVec3 &oldOrigin, const idVec3 &oldVelocity); + + hhPhysics_Player physicsObj; + + idEntityPtr player; + + idAngles viewAngles; + idMat3 eyeAxis; + + idEntityPtr spiritFx; + + int cachedCurrentWeapon; + int activationTime; // Saved to keep the player from being bounced back into the body too quickly if damaged + + //rww - only matters for client keeping track of if animation has started. no need to save/restore. + bool clientAnimated; + //rww - only for telling clients which anim we are playing + int netAnimType; + + //rww - for keeping track of player model changes in mp + int playerModelNum; + + //rww - showing player status icons on the proxy in mp + //proxy could optionally support lag/chat/etc if we wanted to sync those states on the proxy itself + //idPlayerIcon playerIcon; + hhPlayerTeamIcon playerTeamIcon; +}; + +/* +===================== +hhSpiritProxy::IsWallWalking +===================== +*/ +ID_INLINE bool hhSpiritProxy::IsWallWalking() const { + return physicsObj.IsWallWalking(); +} + +/* +===================== +hhSpiritProxy::IsCrouching +===================== +*/ +ID_INLINE bool hhSpiritProxy::IsCrouching() const { + return physicsObj.IsCrouching(); +} + +/* +===================== +hhSpiritProxy::ShouldRemainAlignedToAxial +===================== +*/ +ID_INLINE bool hhSpiritProxy::ShouldRemainAlignedToAxial() { + return physicsObj.ShouldRemainAlignedToAxial(); +} + +// DEATH PROXY ================================================================ + +class hhDeathProxy : public hhSpiritProxy { +public: + CLASS_PROTOTYPE( hhDeathProxy ); + + ~hhDeathProxy(); + + void Spawn(); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + virtual void StartAnimation(); + +protected: + void Event_SpawnEffect(); + void Event_Activate(); + +protected: + idVec3 lastPhysicalLocation; // location to return to from deathwalk + idMat3 lastPhysicalAxis; // orientation to return to from deathwalk +}; + +// MP DEATHWALK PROXY ================================================================ +//rww - for corpses in multiplayer +class hhMPDeathProxy : public hhDeathProxy { +public: + CLASS_PROTOTYPE( hhMPDeathProxy ); + + void Spawn(); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3 &bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + + virtual void Event_CorpseRemove(void); + + void SetFling(const idVec3 &point, const idVec3 &force); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual void ClientPredictionThink( void ); + +private: + bool hasInitial; + bool didFling; + idVec3 initialPos; + idCQuat initialRot; + idVec3 initialFlingForce; +}; + +// DEATHWALK PROXY ================================================================ +//rww - this is the entity that sits in the middle of deathwalk and moves up/down. + +class hhDeathWalkProxy : public hhSpiritProxy { +public: + CLASS_PROTOTYPE( hhDeathWalkProxy ); + + void Spawn(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think(); + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + void SetInitialPos(const idVec3 &pos); + virtual void StartAnimation(); + +protected: + idVec3 initialPos; + float bodyMoveScale; + int timeSinceStage2Started; +}; + +// POSSESSED PROXY ================================================================ +// An invisible proxy that is spawned when the player is possessed + +class hhPossessedProxy : public hhSpiritProxy { +public: + CLASS_PROTOTYPE( hhPossessedProxy ); + + virtual void ActivateProxy( hhPlayer *owner, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ); + +protected: +}; + +#endif /* __PREY_SPIRITPROXY_H__ */ diff --git a/neo/prey/Prey/prey_spiritsecret.cpp b/neo/prey/Prey/prey_spiritsecret.cpp new file mode 100644 index 00000000..a06337c6 --- /dev/null +++ b/neo/prey/Prey/prey_spiritsecret.cpp @@ -0,0 +1,72 @@ +//************************************************************************** +//** +//** PREY_SPIRITSECRET.CPP +//** +//** Game code for the spirit secret entities +//** Spirit secret entities are entities that are only visible +//** and blockable in normal mode. +//************************************************************************** + +// HEADER FILES ------------------------------------------------------------ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// CLASS DECLARATIONS ------------------------------------------------------ + +CLASS_DECLARATION( idEntity, hhSpiritSecret ) + EVENT( EV_Activate, hhSpiritSecret::Event_Activate ) +END_CLASS + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// hhSpiritSecret::Spawn +// +//========================================================================== + +void hhSpiritSecret::Spawn(void) { + fl.takedamage = false; + + if ( !spawnArgs.GetInt( "start_off" ) ) { + GetPhysics()->SetContents( CONTENTS_FORCEFIELD | CONTENTS_SHOOTABLE ); + } else { // Start the secret off + GetPhysics()->SetContents( 0 ); + Hide(); + } + + SetShaderParm( SHADERPARM_MODE, gameLocal.random.CRandomFloat() ); +} + +//========================================================================== +// +// hhSpiritSecret::Event_Activate +// +//========================================================================== + +void hhSpiritSecret::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + GetPhysics()->SetContents( CONTENTS_FORCEFIELD | CONTENTS_SHOOTABLE ); + } else { + Hide(); + GetPhysics()->SetContents( 0 ); + } +} diff --git a/neo/prey/Prey/prey_spiritsecret.h b/neo/prey/Prey/prey_spiritsecret.h new file mode 100644 index 00000000..a23b2b53 --- /dev/null +++ b/neo/prey/Prey/prey_spiritsecret.h @@ -0,0 +1,15 @@ + +#ifndef __PREY_SPIRITSECRET_H__ +#define __PREY_SPIRITSECRET_H__ + +class hhSpiritSecret : public idEntity { +public: + CLASS_PROTOTYPE( hhSpiritSecret ); + + void Spawn( void ); + +protected: + void Event_Activate( idEntity *activator ); +}; + +#endif /* __PREY_SPIRITSECRET_H__ */ diff --git a/neo/prey/Prey/prey_vehiclefirecontroller.cpp b/neo/prey/Prey/prey_vehiclefirecontroller.cpp new file mode 100644 index 00000000..7e7e7e69 --- /dev/null +++ b/neo/prey/Prey/prey_vehiclefirecontroller.cpp @@ -0,0 +1,225 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhFireController, hhVehicleFireController ) +END_CLASS + +/* +================ +hhVehicleFireController::Init +================ +*/ +void hhVehicleFireController::Init( const idDict* viewDict, hhVehicle* self, idActor* owner ) { + hhFireController::Init( viewDict ); + + this->owner = owner; + this->self = self; + + recoil = dict->GetFloat( "recoil" ); + + if ( owner && owner->IsType( idAI::Type ) ) { + spread = dict->GetFloat( "monster_spread" ); + } + + SetBarrelOffsetList( "muzzleOffset", barrelOffsets ); +} + +/* +================ +hhVehicleFireController::UsesCrosshair +================ +*/ +bool hhVehicleFireController::UsesCrosshair() const { + return bCrosshair; +} + +/* +================ +hhVehicleFireController::Clear +================ +*/ +void hhVehicleFireController::Clear() { + hhFireController::Clear(); + + nextFireTime = gameLocal.GetTime(); + barrelOffsets.Clear(); + + owner = NULL; + self = NULL; +} + +/* +================ +hhVehicleFireController::Save +================ +*/ +void hhVehicleFireController::Save( idSaveGame *savefile ) const { + self.Save( savefile ); + owner.Save( savefile ); + savefile->WriteFloat( recoil ); + + int num = barrelOffsets.Num(); + savefile->WriteInt( num ); + for( int i = 0; i < num; i++ ) { + savefile->WriteVec3( barrelOffsets[i] ); + } + + savefile->WriteInt( nextFireTime ); +} + +/* +================ +hhVehicleFireController::Restore +================ +*/ +void hhVehicleFireController::Restore( idRestoreGame *savefile ) { + self.Restore( savefile ); + owner.Restore( savefile ); + savefile->ReadFloat( recoil ); + + int num; + savefile->ReadInt( num ); + idVec3 tmp; + for( int i = 0; i < num; i++ ) { + savefile->ReadVec3( tmp ); + barrelOffsets.Append( tmp ); + } + + savefile->ReadInt( nextFireTime ); +} + +/* +================ +hhVehicleFireController::WeaponFeedback +================ +*/ +void hhVehicleFireController::WeaponFeedback() { + if( self.IsValid() && self->GetPhysics() ) { + hhPhysics_Vehicle* selfPhysics = static_cast( self->GetPhysics() ); + self->ApplyImpulse( gameLocal.world, 0, self->GetOrigin() + selfPhysics->GetCenterOfMass(), -self->GetAxis()[0] * GetRecoil() * selfPhysics->GetMass() ); + } +} + +/* +================ +hhVehicleFireController::SetBarrelOffsetList +================ +*/ +void hhVehicleFireController::SetBarrelOffsetList( const char* keyPrefix, hhCycleList& offsetList ) { + const idKeyValue* kv = dict->MatchPrefix( keyPrefix ); + while( kv ) { + offsetList.Append( dict->GetVector(kv->GetKey().c_str()) ); + kv = dict->MatchPrefix( keyPrefix, kv ); + } +} + +/* +================= +hhVehicleFireController::DetermineAimAxis +================= +*/ +idMat3 hhVehicleFireController::DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ) { + idAngles aimAngles; + idVec3 aimPos; + + aimPos = idVec3( 0.0f, 0.0f, owner->EyeHeight() ) + dict->GetVector( "offset_gunTarget" ); + aimPos *= self->GetFireAxis(); + aimPos += self->GetFireOrigin(); + + aimAngles = (aimPos - muzzlePos).ToAngles(); + aimAngles[2] = weaponAxis.ToAngles()[2]; + return aimAngles.ToMat3(); +} + +/* +================ +hhVehicleFireController::LaunchProjectiles +================ +*/ +bool hhVehicleFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + if( nextFireTime > gameLocal.GetTime() ) { + return false; + } + + if( !hhFireController::LaunchProjectiles(pushVelocity) ) { + return false; + } + + gameLocal.AlertAI( owner.GetEntity() ); + nextFireTime = gameLocal.GetTime() + SEC2MS( GetFireDelay() ); + return true; +} + +/* +================ +hhFireController::AmmoAvailable +================ +*/ +int hhVehicleFireController::AmmoAvailable() const { + if ( self.IsValid() ) { + return self->HasPower( AmmoRequired() ); + } else { + return 0; + } +} + +/* +================ +hhFireController::UseAmmo +================ +*/ +void hhVehicleFireController::UseAmmo() { + if( self.IsValid() ) { + self->ConsumePower( AmmoRequired() ); + } +} + +/* +================ +hhVehicleFireController::GetCollisionBBox +================ +*/ +const idBounds& hhVehicleFireController::GetCollisionBBox() { + return self->GetPhysics()->GetAbsBounds(); +} + +/* +================ +hhVehicleFireController::CalculateMuzzlePosition +================ +*/ +void hhVehicleFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + axis = self->GetFireAxis(); + origin = barrelOffsets.Next() * axis + self->GetFireOrigin(); +} + +/* +================ +hhVehicleFireController::GetProjectileOwner +================ +*/ +idEntity *hhVehicleFireController::GetProjectileOwner() const { + return self.GetEntity(); +} + +/* +================ +hhVehicleFireController::GetSelf +================ +*/ +hhRenderEntity *hhVehicleFireController::GetSelf() { + return self.GetEntity(); +} + +/* +================ +hhVehicleFireController::GetSelfConst +================ +*/ +const hhRenderEntity *hhVehicleFireController::GetSelfConst() const { + return self.GetEntity(); +} + diff --git a/neo/prey/Prey/prey_vehiclefirecontroller.h b/neo/prey/Prey/prey_vehiclefirecontroller.h new file mode 100644 index 00000000..546f198d --- /dev/null +++ b/neo/prey/Prey/prey_vehiclefirecontroller.h @@ -0,0 +1,47 @@ +#ifndef __HH_VEHICLE_FIRE_CONTROLLER_H +#define __HH_VEHICLE_FIRE_CONTROLLER_H + +#include "gamesys/Class.h" + +class hhVehicleFireController : public hhFireController { + CLASS_PROTOTYPE(hhVehicleFireController); + +public: + virtual void Clear(); + virtual void Init( const idDict* viewDict, hhVehicle* self, idActor* owner ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); + virtual void WeaponFeedback(); + virtual idMat3 DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ); + + virtual int AmmoAvailable() const; + virtual void UseAmmo(); + + virtual float GetRecoil() const { return recoil; } + virtual bool UsesCrosshair() const; + + //rww - made public + hhCycleList barrelOffsets; + +protected: + virtual const idBounds& GetCollisionBBox(); + virtual idEntity* GetProjectileOwner() const; + void SetBarrelOffsetList( const char* keyPrefix, hhCycleList& offsetList ); + void SaveBarrelOffsetList( const hhCycleList& offsetList, idSaveGame *savefile ) const; + void RestoreBarrelOffsetList( hhCycleList& offsetList, idRestoreGame *savefile ); + + virtual void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); + virtual hhRenderEntity *GetSelf(); + virtual const hhRenderEntity *GetSelfConst() const; + +protected: + idEntityPtr self; + idEntityPtr owner; + + float recoil; + int nextFireTime; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weapon.cpp b/neo/prey/Prey/prey_weapon.cpp new file mode 100644 index 00000000..5fe860ce --- /dev/null +++ b/neo/prey/Prey/prey_weapon.cpp @@ -0,0 +1,4 @@ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "prey_local.h" diff --git a/neo/prey/Prey/prey_weapon.h b/neo/prey/Prey/prey_weapon.h new file mode 100644 index 00000000..7663fc62 --- /dev/null +++ b/neo/prey/Prey/prey_weapon.h @@ -0,0 +1,5 @@ +#ifndef __PREY_WEAPON_H__ +#define __PREY_WEAPON_H__ + + +#endif /* !__PREY_WEAPON_H__ */ diff --git a/neo/prey/Prey/prey_weaponautocannon.cpp b/neo/prey/Prey/prey_weaponautocannon.cpp new file mode 100644 index 00000000..010db18a --- /dev/null +++ b/neo/prey/Prey/prey_weaponautocannon.cpp @@ -0,0 +1,363 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponAutoCannon + +***********************************************************************/ +const idEventDef EV_Weapon_AdjustHeat( "adjustHeat", "f" ); +const idEventDef EV_Weapon_GetHeatLevel( "getHeatLevel", "", 'f' ); + +const idEventDef EV_Weapon_SpawnRearGasFX( "spawnRearGasFX" ); +const idEventDef EV_SpawnSparkLocal( "", "s" ); + +const idEventDef EV_Broadcast_AssignLeftRearFx( "", "e" ); +const idEventDef EV_Broadcast_AssignRightRearFx( "", "e" ); + +const idEventDef EV_Weapon_OverHeatNetEvent( "overHeatNetEvent" ); //rww + +CLASS_DECLARATION( hhWeapon, hhWeaponAutoCannon ) + EVENT( EV_Weapon_AdjustHeat, hhWeaponAutoCannon::Event_AdjustHeat ) + EVENT( EV_Weapon_GetHeatLevel, hhWeaponAutoCannon::Event_GetHeatLevel ) + EVENT( EV_Weapon_SpawnRearGasFX, hhWeaponAutoCannon::Event_SpawnRearGasFX ) + EVENT( EV_SpawnSparkLocal, hhWeaponAutoCannon::Event_SpawnSparkLocal ) + EVENT( EV_Broadcast_AssignLeftRearFx, hhWeaponAutoCannon::Event_AssignLeftRearFx ) + EVENT( EV_Broadcast_AssignRightRearFx, hhWeaponAutoCannon::Event_AssignRightRearFx ) + EVENT( EV_Weapon_OverHeatNetEvent, hhWeaponAutoCannon::Event_OverHeatNetEvent ) +END_CLASS + +/* +================ +hhWeaponAutoCannon::Spawn +================ +*/ +void hhWeaponAutoCannon::Spawn() { + BecomeActive( TH_TICKER ); + + beamSystem.Clear(); + rearGasFxL.Clear(); + rearGasFxR.Clear(); +} + +/* +================ +hhWeaponAutoCannon::~hhWeaponAutoCannon +================ +*/ +hhWeaponAutoCannon::~hhWeaponAutoCannon() { + SAFE_REMOVE( beamSystem ); + SAFE_REMOVE( rearGasFxL ); + SAFE_REMOVE( rearGasFxR ); +} + +/* +================ +hhWeaponAutoCannon::ParseDef +================ +*/ +void hhWeaponAutoCannon::ParseDef( const char *objectname ) { + hhWeapon::ParseDef( objectname ); + + SetHeatLevel( 0.0f ); + + InitBoneInfo(); + + if (owner.IsValid() && owner.GetEntity() && owner.GetEntity() == gameLocal.GetLocalPlayer()) { //rww - let's make the beam locally, since it is a client entity. no need for the server to do anything. + ProcessEvent( &EV_SpawnSparkLocal, dict->GetString("beam_spark") ); + } + //BroadcastBeam( dict->GetString("beam_spark"), EV_SpawnSparkLocal ); +} + +/* +================ +hhWeaponAutoCannon::UpdateGUI +================ +*/ +void hhWeaponAutoCannon::UpdateGUI() { + if ( GetRenderEntity()->gui[ 0 ] && state != idStr(WP_HOLSTERED) ) { + GetRenderEntity()->gui[ 0 ]->SetStateFloat( "temperature", GetHeatLevel() ); + } +} + +/* +================ +hhWeaponAutoCannon::Ticker +================ +*/ +void hhWeaponAutoCannon::Ticker() { + idVec3 boneOriginL, boneOriginR; + idMat3 boneAxisL, boneAxisR; + + if( beamSystem.IsValid() ) { + GetJointWorldTransform( sparkBoneL, boneOriginL, boneAxisL ); + GetJointWorldTransform( sparkBoneR, boneOriginR, boneAxisR ); + + if( (boneOriginL - boneOriginR).Length() > sparkGapSize ) { + if( !beamSystem->IsHidden() ) { + beamSystem->Hide(); + SetShaderParm( SHADERPARM_MODE, 0.0f ); + } + } else if ( owner->CanShowWeaponViewmodel() ) { + if( beamSystem->IsHidden() ) { + beamSystem->Show(); + SetShaderParm( SHADERPARM_MODE, 1.0f ); + } + } + + beamSystem->SetOrigin( boneOriginL ); + beamSystem->SetTargetLocation( boneOriginR ); + } +} + +/* +================ +hhWeaponAutoCannon::InitBoneInfo +================ +*/ +void hhWeaponAutoCannon::InitBoneInfo() { + assert( dict ); + + GetJointHandle( dict->GetString("joint_sparkL"), sparkBoneL ); + GetJointHandle( dict->GetString("joint_sparkR"), sparkBoneR ); +} + +/* +================ +hhWeaponAutoCannon::SetHeatLevel +================ +*/ +void hhWeaponAutoCannon::SetHeatLevel( const float heatLevel ) { + this->heatLevel = heatLevel; + + SetShaderParm( SHADERPARM_MISC, heatLevel ); +} + +/* +================ +hhWeaponAutoCannon::AdjustHeat +================ +*/ +void hhWeaponAutoCannon::AdjustHeat( const float amount ) { + SetHeatLevel( hhMath::ClampFloat(0.0f, 1.0f, GetHeatLevel() + amount) ); + +//#if _DEBUG +// gameLocal.Printf("HeatLevel: %.2f\n", GetHeatLevel()); +//#endif +} + +/* +================ +hhWeaponAutoCannon::PresentWeapon +================ +*/ +void hhWeaponAutoCannon::PresentWeapon( bool showViewModel ) { + if( IsHidden() || !owner->CanShowWeaponViewmodel() || pm_thirdPerson.GetBool() ) { + if( beamSystem.IsValid() ) { + beamSystem->Activate( false ); + } + } else { + if( beamSystem.IsValid() ) { + beamSystem->Activate( true ); + } + } + + hhWeapon::PresentWeapon( showViewModel ); +} + +void hhWeaponAutoCannon::Show() { + if ( beamSystem.IsValid() ) + beamSystem->Show(); + hhWeapon::Show(); +} + +void hhWeaponAutoCannon::Hide() { + if ( beamSystem.IsValid() ) + beamSystem->Hide(); + hhWeapon::Hide(); +} + +void hhWeaponAutoCannon::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeapon::WriteToSnapshot( msg ); + + msg.WriteFloat( GetHeatLevel() ); +} + +void hhWeaponAutoCannon::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeapon::ReadFromSnapshot( msg ); + + SetHeatLevel( msg.ReadFloat() ); +} + +/* +================ +hhWeaponAutoCannon::Event_SpawnSparkLocal +================ +*/ +void hhWeaponAutoCannon::Event_SpawnSparkLocal( const char* defName ) { + assert(dict); //rww - this could happen if the server sent a beam event before we initialized the weap on the client + sparkGapSize = dict->GetFloat( "sparkGapSize" ); + + SAFE_REMOVE( beamSystem ); + beamSystem = hhBeamSystem::SpawnBeam( GetOrigin(), defName, mat3_identity, true ); + if( !beamSystem.IsValid() ) { + return; + } + + //rww - this particular beam not a network entity + beamSystem->fl.networkSync = false; + beamSystem->fl.clientEvents = true; + + beamSystem->fl.neverDormant = true; + beamSystem->GetRenderEntity()->weaponDepthHack = true; + beamSystem->GetRenderEntity()->allowSurfaceInViewID = owner->entityNumber + 1; +} + +/* +================ +hhWeaponAutoCannon::Event_SpawnRearGasFX +================ +*/ +void hhWeaponAutoCannon::Event_SpawnRearGasFX() { + if( !owner->CanShowWeaponViewmodel() || pm_thirdPerson.GetBool() ) + return; + + hhFxInfo fxInfo; + + float fxHeatThreshold = hhMath::ClampFloat( 0.0f, 1.0f, dict->GetFloat("heatThreshold") ); + if( fxHeatThreshold >= GetHeatLevel() ) { + return; + } + + //Checking if fx's are done yet. Only want to get in when fx's are done + if( !rearGasFxL.IsValid() && !rearGasFxR.IsValid() ) { + fxInfo.UseWeaponDepthHack( true ); + fxInfo.RemoveWhenDone( true ); + + BroadcastFxInfoAlongBone( dict->RandomPrefix("fx_rearGas", gameLocal.random), dict->GetString("joint_rearGasFxL"), &fxInfo, &EV_Broadcast_AssignLeftRearFx, false ); + BroadcastFxInfoAlongBone( dict->RandomPrefix("fx_rearGas", gameLocal.random), dict->GetString("joint_rearGasFxR"), &fxInfo, &EV_Broadcast_AssignRightRearFx, false ); + + if( GetHeatLevel() >= 1.0f ) + StartSound( "snd_overheat", SND_CHANNEL_BODY, 0, false, NULL ); + else + StartSound( "snd_steam_vent", SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +================ +hhWeaponAutoCannon::Event_AssignLeftRearFx +================ +*/ +void hhWeaponAutoCannon::Event_AssignLeftRearFx( hhEntityFx* fx ) { + rearGasFxL = fx; +} + +/* +================ +hhWeaponAutoCannon::Event_AssignRightRearFx +================ +*/ +void hhWeaponAutoCannon::Event_AssignRightRearFx( hhEntityFx* fx ) { + rearGasFxR = fx; +} + +/* +================ +hhWeaponAutoCannon::Event_AdjustHeat +================ +*/ +void hhWeaponAutoCannon::Event_AdjustHeat( const float fAmount ) { + if (gameLocal.isClient) { //rww - don't adjust on client + return; + } + AdjustHeat( fAmount ); +} + +/* +================ +hhWeaponAutoCannon::Event_GetHeatLevel +================ +*/ +void hhWeaponAutoCannon::Event_GetHeatLevel() { + idThread::ReturnFloat( GetHeatLevel() ); +} + +/* +================ +hhWeaponAutoCannon::Event_OverHeatNetEvent +================ +*/ +void hhWeaponAutoCannon::Event_OverHeatNetEvent() { //rww + if (gameLocal.isClient) { + return; + } + + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + ServerSendEvent( EVENT_OVERHEAT, &msg, false, -1 ); +} + +/* +================ +hhWeaponAutoCannon::ClientReceiveEvent +================ +*/ +bool hhWeaponAutoCannon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { //rww + switch (event) { + case EVENT_OVERHEAT: { + SetState("OverHeated", 10); + return true; + } + default: { + return hhWeapon::ClientReceiveEvent( event, time, msg ); + } + } +} + + +/* +================ +hhWeaponAutoCannon::Save +================ +*/ +void hhWeaponAutoCannon::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( heatLevel ); + savefile->WriteFloat( sparkGapSize ); + + beamSystem.Save( savefile ); + + savefile->WriteInt( sparkBoneL.view ); + savefile->WriteInt( sparkBoneL.world ); + + savefile->WriteInt( sparkBoneR.view ); + savefile->WriteInt( sparkBoneR.world ); + + rearGasFxL.Save( savefile ); + rearGasFxR.Save( savefile ); +} + +/* +================ +hhWeaponAutoCannon::Restore +================ +*/ +void hhWeaponAutoCannon::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( heatLevel ); + savefile->ReadFloat( sparkGapSize ); + + beamSystem.Restore( savefile ); + + savefile->ReadInt( reinterpret_cast ( sparkBoneL.view ) ); + savefile->ReadInt( reinterpret_cast ( sparkBoneL.world ) ); + + savefile->ReadInt( reinterpret_cast ( sparkBoneR.view ) ); + savefile->ReadInt( reinterpret_cast ( sparkBoneR.world ) ); + + rearGasFxL.Restore( savefile ); + rearGasFxR.Restore( savefile ); +} diff --git a/neo/prey/Prey/prey_weaponautocannon.h b/neo/prey/Prey/prey_weaponautocannon.h new file mode 100644 index 00000000..0df8ebb3 --- /dev/null +++ b/neo/prey/Prey/prey_weaponautocannon.h @@ -0,0 +1,67 @@ +#ifndef __HH_WEAPON_AUTOCANNON_H +#define __HH_WEAPON_AUTOCANNON_H + +/*********************************************************************** + + hhWeaponAutoCannon + +***********************************************************************/ +class hhWeaponAutoCannon : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponAutoCannon ); + + public: + enum { + EVENT_OVERHEAT = hhWeapon::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + void Spawn(); + virtual ~hhWeaponAutoCannon(); + + virtual void ParseDef( const char* objectname ); + virtual void UpdateGUI(); + + virtual void Show(); + virtual void Hide(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + protected: + void Ticker(); + void AdjustHeat( const float amount ); + + void InitBoneInfo(); + void SpawnSpark(); + + void SetHeatLevel( const float heatLevel ); + float GetHeatLevel() const { return heatLevel; } + + virtual void PresentWeapon( bool showViewModel ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + protected: + void Event_SpawnRearGasFX(); + void Event_AssignLeftRearFx( hhEntityFx* fx ); + void Event_AssignRightRearFx( hhEntityFx* fx ); + void Event_AdjustHeat( const float amount ); + void Event_GetHeatLevel(); + void Event_SpawnSparkLocal( const char* defName ); + void Event_OverHeatNetEvent(); //rww + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); //rww + + protected: + float heatLevel; + + float sparkGapSize; + idEntityPtr beamSystem; + weaponJointHandle_t sparkBoneL; + weaponJointHandle_t sparkBoneR; + + idEntityPtr rearGasFxL; + idEntityPtr rearGasFxR; +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponcrawlergrenade.cpp b/neo/prey/Prey/prey_weaponcrawlergrenade.cpp new file mode 100644 index 00000000..7dd83a65 --- /dev/null +++ b/neo/prey/Prey/prey_weaponcrawlergrenade.cpp @@ -0,0 +1,51 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponCrawlerGrenade + +***********************************************************************/ +const idEventDef EV_SpawnBloodSpray( "spawnBloodSpray" ); + +CLASS_DECLARATION( hhWeapon, hhWeaponCrawlerGrenade ) + EVENT( EV_SpawnBloodSpray, hhWeaponCrawlerGrenade::Event_SpawnBloodSpray ) +END_CLASS + +/* +================= +hhWeaponCrawlerGrenade::WriteToSnapshot +================= +*/ +void hhWeaponCrawlerGrenade::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeapon::WriteToSnapshot(msg); + msg.WriteBits(WEAPON_ALTMODE, 1); +} + +/* +================= +hhWeaponCrawlerGrenade::ReadFromSnapshot +================= +*/ +void hhWeaponCrawlerGrenade::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeapon::ReadFromSnapshot(msg); + WEAPON_ALTMODE = !!msg.ReadBits(1); +} + +/* +================= +hhWeaponCrawlerGrenade::Event_SpawnBloodSpray +================= +*/ +void hhWeaponCrawlerGrenade::Event_SpawnBloodSpray() { + hhFxInfo fxInfo; + fxInfo.UseWeaponDepthHack( true ); + if (WEAPON_ALTMODE) { + BroadcastFxInfoAlongBonePrefix( dict, "fx_blood", "joint_AltBloodFx", &fxInfo ); + } + else { + BroadcastFxInfoAlongBonePrefix( dict, "fx_blood", "joint_bloodFx", &fxInfo ); + } +} \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponcrawlergrenade.h b/neo/prey/Prey/prey_weaponcrawlergrenade.h new file mode 100644 index 00000000..d1842f20 --- /dev/null +++ b/neo/prey/Prey/prey_weaponcrawlergrenade.h @@ -0,0 +1,21 @@ +#ifndef __HH_WEAPON_CRAWLERGRENADE_H +#define __HH_WEAPON_CRAWLERGRENADE_H + +/*********************************************************************** + + hhWeaponCrawlerGrenade + +***********************************************************************/ +class hhWeaponCrawlerGrenade : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponCrawlerGrenade ); + + public: + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + protected: + void Event_SpawnBloodSpray(); +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponfirecontroller.cpp b/neo/prey/Prey/prey_weaponfirecontroller.cpp new file mode 100644 index 00000000..03b553ef --- /dev/null +++ b/neo/prey/Prey/prey_weaponfirecontroller.cpp @@ -0,0 +1,574 @@ + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +CLASS_DECLARATION( hhFireController, hhWeaponFireController ) +END_CLASS + +/* +================ +hhWeaponFireController::Clear +================ +*/ +void hhWeaponFireController::Clear() { + hhFireController::Clear(); + + self = NULL; + owner = NULL; + + brassDef = NULL; + brassDelay = -1; + + ammoType = 0; + clipSize = 0; // 0 means no reload + ammoClip = 0; + lowAmmo = 0; + + // weapon kick + muzzle_kick_time = 0; + muzzle_kick_maxtime = 0; + muzzle_kick_angles.Zero(); + muzzle_kick_offset.Zero(); + + aimDist.Zero(); + + // joints from models + barrelJoints.Clear(); + ejectJoints.Clear(); +} + +/* +================ +hhWeaponFireController::Init +================ +*/ +void hhWeaponFireController::Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ) { + const char *brassDefName = NULL; + + hhFireController::Init( viewDict ); + + this->self = self; + this->owner = owner; + + muzzleFlash.lightId = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + //muzzleFlash.allowLightInViewID = owner->entityNumber+1; + + if( !dict ) { + return; + } + + muzzle_kick_time = SEC2MS( dict->GetFloat( "muzzle_kick_time" ) ); + muzzle_kick_maxtime = SEC2MS( dict->GetFloat( "muzzle_kick_maxtime" ) ); + muzzle_kick_angles = dict->GetAngles( "muzzle_kick_angles" ); + muzzle_kick_offset = dict->GetVector( "muzzle_kick_offset" ); + + // find some joints in the model for locating effects + SetWeaponJointHandleList( "joint_barrel", barrelJoints ); + SetWeaponJointHandleList( "joint_eject", ejectJoints ); + + // get the brass def + SetBrassDict( dict->GetString("def_ejectBrass") ); + + ammoType = GetAmmoType( dict->GetString( "ammoType" ) ); + clipSize = dict->GetInt( "clipSize" ); + lowAmmo = dict->GetInt( "lowAmmo" ); + + if( ( ammoType < 0 ) || ( ammoType >= AMMO_NUMTYPES ) ) { + gameLocal.Warning( "Unknown ammotype in object '%s'", dict->GetString("classname") ); + } + + //HUMANHEAD bjk: set in hhWeapon::GetWeaponDef + ammoClip = 0; + /*if ( clipSize ) { + ammoClip = owner->HasAmmo( ammoType, ammoRequired ); + if ( ( ammoClip < 0 ) || ( ammoClip > clipSize ) ) { + // first time using this weapon so have it fully loaded to start + ammoClip = clipSize; + } + }*/ + + aimDist = dict->GetVec2( "aimDist", "20 50" ); + scriptFunction = dict->GetString( "script_function", "Fire" ); +} + +/* +================ +hhWeaponFireController::SetWeaponJointHandleList +================ +*/ +void hhWeaponFireController::SetWeaponJointHandleList( const char* keyPrefix, hhCycleList& jointList ) { + weaponJointHandle_t handle; + const idKeyValue* kv = dict->MatchPrefix( keyPrefix ); + while( kv ) { + self->GetJointHandle( kv->GetValue().c_str(), handle ); + jointList.Append( handle ); + kv = dict->MatchPrefix( keyPrefix, kv ); + } +} + +/* +================ +hhWeaponFireController::SaveWeaponJointHandleList +================ +*/ +void hhWeaponFireController::SaveWeaponJointHandleList( const hhCycleList& jointList, idSaveGame *savefile ) const { + const weaponJointHandle_t* handle = NULL; + + int num = jointList.Num(); + savefile->WriteInt( num ); + for( int ix = 0; ix < num; ++ix ) { + handle = &jointList[ix]; + if( handle ) { + savefile->WriteInt( handle->view ); + savefile->WriteInt( handle->world ); + } + } +} + +/* +================ +hhWeaponFireController::RestoreWeaponJointHandleList +================ +*/ +void hhWeaponFireController::RestoreWeaponJointHandleList( hhCycleList& jointList, idRestoreGame *savefile ) { + //weaponJointHandle_t* handle = NULL; + + jointList.Clear(); + + int num; + savefile->ReadInt( num ); + weaponJointHandle_t handle; + for( int ix = 0; ix < num; ++ix ) { + savefile->ReadInt( (int&)handle.view ); + savefile->ReadInt( (int&)handle.world ); + jointList.Append( handle ); + } +} + +/* +================ +hhWeaponFireController::SetBrassDict +================ +*/ +void hhWeaponFireController::SetBrassDict( const char* name ) { + brassDefName = name; + if ( name[0] ) { + brassDef = gameLocal.FindEntityDefDict( name, false ); + if ( !brassDef ) { + gameLocal.Warning( "Unknown brass '%s'", name ); + } + } else { + brassDef = NULL; + } +} + +/* +================ +hhWeaponFireController::LaunchProjectiles +================ +*/ +bool hhWeaponFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + if( !hhFireController::LaunchProjectiles(pushVelocity) ) { + return false; + } + + if( !gameLocal.isClient ) { + owner->AddProjectilesFired( numProjectiles ); + } + + //HUMANHEAD bjk PATCH 7-27-06 + if( gameLocal.isMultiplayer ) { + owner->inventory.lastShot[owner->GetWeaponNum( self->spawnArgs.GetString( "classname" ) )] = gameLocal.time + 1000; + } + + return true; +} + +/* +================= +hhWeaponFireController::DetermineAimAxis +================= +*/ +idMat3 hhWeaponFireController::DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ) { + float traceDist = 1024.0f; // was CM_MAX_TRACE_DIST + float aimTraceDist; + trace_t aimTrace = self->GetEyeTraceInfo(); + + //HUMANHEAD bjk + if( aimTrace.fraction < 1.0f ) { + idVec3 eyePos = owner->GetEyePosition(); + + // Perform eye trace + gameLocal.clip.TracePoint( aimTrace, eyePos, eyePos + weaponAxis[0] * traceDist * 4.0f, + MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + + if ( aimTrace.fraction < 1.0f ) { // CJR: Check for portals + idEntity *ent = gameLocal.entities[ aimTrace.c.entityNum ]; // cannot use GetTraceEntity() as the portal could be bound to something + if ( ent && ent->IsType( hhPortal::Type ) ) { + aimTrace.endpos = eyePos + weaponAxis[0] * traceDist; + aimTrace.fraction = 1.0f; + } + } + + aimTraceDist = aimTrace.fraction * traceDist * 4.0f; + } else { + aimTraceDist = aimTrace.fraction * traceDist; + } //HUMANHEAD END + + idVec3 aimVector( aimTrace.endpos - muzzlePos ); + idVec3 aimDir = aimVector; + + aimDir.Normalize(); + aimDir.Lerp( weaponAxis[0], aimDir, hhUtils::CalculateScale(aimTraceDist, aimDist[0], aimDist[1]) ); + + idAngles aimAngles( aimDir.ToAngles() ); + + aimAngles.roll = weaponAxis.ToAngles().roll; + return aimAngles.ToMat3(); +} + +/* +================ +hhWeaponFireController::WeaponFeedback +================ +*/ +void hhWeaponFireController::WeaponFeedback() { + if( owner.IsValid() ) { + owner->WeaponFireFeedback( dict ); + } +} + +/* +================ +hhWeaponFireController::EjectBrass +================ +*/ +void hhWeaponFireController::EjectBrass() { + idMat3 axis; + idVec3 origin; + idEntity* ent = NULL; + idDebris* brass = NULL; + + if( !g_showBrass.GetBool() || !brassDef ) { + return; + } + + if (gameLocal.isClient && !gameLocal.isNewFrame) { //rww + return; + } + + if( TransformBrass(ejectJoints.Next(), origin, axis) ) { + gameLocal.SpawnEntityDef( *brassDef, &ent, true, gameLocal.isClient ); //rww - localized debris + HH_ASSERT( ent && ent->IsType(idDebris::Type) ); + + brass = static_cast( ent ); + brass->Create( owner.GetEntity(), origin, axis ); + brass->Launch(); + brass->fl.networkSync = false; //rww + brass->fl.clientEvents = true; //rww + //HACK: this should be in debris object. Just not sure if I should re-write it or not. + brass->SetShaderParm( SHADERPARM_TIME_OF_DEATH, MS2SEC(gameLocal.GetTime()) ); + } +} + +/* +================= +hhWeaponFireController::TransformBrass +================= +*/ +bool hhWeaponFireController::TransformBrass( const weaponJointHandle_t& handle, idVec3 &origin, idMat3 &axis ) { + if( !self->GetJointWorldTransform(handle, origin, axis) ) { + return false; + } + + axis = self->GetAxis(); + + return true; +} + +/* +================ +hhWeaponFireController::CalculateMuzzleRise +================ +*/ +void hhWeaponFireController::CalculateMuzzleRise( idVec3& origin, idMat3& axis ) { + int time; + float amount; + idAngles ang; + idVec3 offset; + + time = self->GetKickEndTime() - gameLocal.GetTime(); + 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; +} + +/* +================ +hhWeaponFireController::UpdateMuzzleKick +================ +*/ +void hhWeaponFireController::UpdateMuzzleKick() { + // add some to the kick time, incrementally moving repeat firing weapons back + if ( self->GetKickEndTime() < gameLocal.GetTime() ) { + self->SetKickEndTime( gameLocal.GetTime() ); + } + self->SetKickEndTime( self->GetKickEndTime() + muzzle_kick_time ); + if ( self->GetKickEndTime() > gameLocal.GetTime() + muzzle_kick_maxtime ) { + self->SetKickEndTime( gameLocal.GetTime() + muzzle_kick_maxtime ); + } +} + +/* +================ +hhWeaponFireController::CalculateMuzzlePosition +================ +*/ +void hhWeaponFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + if( !barrelJoints.Num() || !self->GetJointWorldTransform(barrelJoints.Next(), origin, axis) ) { + origin = GetMuzzlePosition(); + axis = self->GetAxis(); + } +} + +/* +================ +hhWeaponFireController::UseAmmo +================ +*/ +void hhWeaponFireController::UseAmmo() { + if( owner.IsValid() ) { + owner->UseAmmo( GetAmmoType(), AmmoRequired() ); + if ( ClipSize() && AmmoRequired() ) { + ammoClip--; + } + } +} + +/* +================ +hhWeaponFireController::AddToClip +================ +*/ +void hhWeaponFireController::AddToClip( int amount ) { + ammoClip += amount; + if ( ammoClip > clipSize ) { + ammoClip = clipSize; + } + + if ( ammoClip > AmmoAvailable() ) { + ammoClip = AmmoAvailable(); + } +} + +/* +================ +hhWeaponFireController::GetAmmoType +================ +*/ +ammo_t hhWeaponFireController::GetAmmoType( 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 ] ) { + ammoname = "ammo_none"; + } + + if ( !ammoDict->GetInt( ammoname, "-1", num ) ) { + gameLocal.Error( "Unknown ammo type '%s'", ammoname ); + } + + if ( ( num < 0 ) || ( num >= AMMO_NUMTYPES ) ) { + gameLocal.Error( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, AMMO_NUMTYPES ); + } + + return ( ammo_t )num; +} + +/* +================ +hhWeaponFireController::Save +================ +*/ +void hhWeaponFireController::Save( idSaveGame *savefile ) const { + self.Save( savefile ); + owner.Save( savefile ); + + savefile->WriteString( brassDefName ); + savefile->WriteInt( brassDelay ); + savefile->WriteString( scriptFunction ); + + savefile->WriteInt( ammoType ); + savefile->WriteInt( clipSize ); + savefile->WriteInt( ammoClip ); + savefile->WriteInt( lowAmmo ); + + savefile->WriteInt( muzzle_kick_time ); + savefile->WriteInt( muzzle_kick_maxtime ); + savefile->WriteAngles( muzzle_kick_angles ); + savefile->WriteVec3( muzzle_kick_offset ); + + savefile->WriteVec2( aimDist ); + + SaveWeaponJointHandleList( barrelJoints, savefile ); + SaveWeaponJointHandleList( ejectJoints, savefile ); + +} + +/* +================ +hhWeaponFireController::Restore +================ +*/ +void hhWeaponFireController::Restore( idRestoreGame *savefile ) { + self.Restore( savefile ); + owner.Restore( savefile ); + savefile->ReadString( brassDefName ); + savefile->ReadInt( brassDelay ); + savefile->ReadString( scriptFunction ); + + savefile->ReadInt( reinterpret_cast ( ammoType ) ); + savefile->ReadInt( clipSize ); + savefile->ReadInt( ammoClip ); + savefile->ReadInt( lowAmmo ); + + savefile->ReadInt( muzzle_kick_time ); + savefile->ReadInt( muzzle_kick_maxtime ); + savefile->ReadAngles( muzzle_kick_angles ); + savefile->ReadVec3( muzzle_kick_offset ); + + savefile->ReadVec2( aimDist ); + + RestoreWeaponJointHandleList( barrelJoints, savefile ); + RestoreWeaponJointHandleList( ejectJoints, savefile ); + + SetBrassDict( brassDefName ); +} + +/* +================ +hhWeaponFireController::AmmoAvailable +================ +*/ +int hhWeaponFireController::AmmoAvailable() const { + if ( owner.IsValid() ) { + return owner->HasAmmo( GetAmmoType(), AmmoRequired() ); + } else { + return 0; + } +} + +/* +================ +hhWeaponFireController::GetProjectileOwner +================ +*/ +idEntity *hhWeaponFireController::GetProjectileOwner() const { + return owner.GetEntity(); +} + +/* +================ +hhWeaponFireController::GetCollisionBBox +================ +*/ +const idBounds& hhWeaponFireController::GetCollisionBBox() { + return owner->GetPhysics()->GetAbsBounds(); +} + +/* +================ +hhWeaponFireController::GetSelf +================ +*/ +hhRenderEntity *hhWeaponFireController::GetSelf() { + return self.GetEntity(); +} + +/* +================ +hhWeaponFireController::GetSelfConst +================ +*/ +const hhRenderEntity *hhWeaponFireController::GetSelfConst() const { + return self.GetEntity(); +} + +/* +================ +hhWeaponFireController::UsesCrosshair +================ +*/ +bool hhWeaponFireController::UsesCrosshair() const { + return (self.IsValid() && bCrosshair && self->ShowCrosshair()); +} + +//rww - net friendliness +void hhWeaponFireController::WriteToSnapshot( idBitMsgDelta &msg ) const +{ + msg.WriteBits(self.GetSpawnId(), 32); + msg.WriteBits(owner.GetSpawnId(), 32); + + msg.WriteBits(ammoClip, self->GetClipBits()); + + //RWWTODO: Added lowAmmo int in case it needs to be sent --paul +} + +void hhWeaponFireController::ReadFromSnapshot( const idBitMsgDelta &msg ) +{ + self.SetSpawnId(msg.ReadBits(32)); + owner.SetSpawnId(msg.ReadBits(32)); + + ammoClip = msg.ReadBits(self->GetClipBits()); +} + +/* +================ +hhWeaponFireController::UpdateWeaponJoints +================ +*/ +void hhWeaponFireController::UpdateWeaponJoints(void) { //rww + barrelJoints.Clear(); + ejectJoints.Clear(); + + SetWeaponJointHandleList( "joint_barrel", barrelJoints ); + SetWeaponJointHandleList( "joint_eject", ejectJoints ); +} + +/* +================ +hhWeaponFireController::CheckThirdPersonMuzzle +================ +*/ +bool hhWeaponFireController::CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis) { //rww + if (barrelJoints.Num() <= 0) { + return false; + } + + int i = barrelJoints.GetCurrentIndex(); + return self->GetJointWorldTransform(barrelJoints[i], origin, axis, true); +} diff --git a/neo/prey/Prey/prey_weaponfirecontroller.h b/neo/prey/Prey/prey_weaponfirecontroller.h new file mode 100644 index 00000000..ae3185c8 --- /dev/null +++ b/neo/prey/Prey/prey_weaponfirecontroller.h @@ -0,0 +1,138 @@ +#ifndef __HH_WEAPON_FIRE_CONTROLLER_H +#define __HH_WEAPON_FIRE_CONTROLLER_H + +struct weaponJointHandle_t { + jointHandle_t view; + jointHandle_t world; + + weaponJointHandle_t() { Clear(); } + void Clear() { view = INVALID_JOINT; world = INVALID_JOINT; } +}; + +/*********************************************************************** + + hhWeaponFireController + + Base class that manages the firing projectiles. +***********************************************************************/ +class hhWeaponFireController : public hhFireController { + CLASS_PROTOTYPE(hhWeaponFireController) + +public: + virtual void Clear(); + virtual void Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); + virtual idMat3 DetermineAimAxis( const idVec3& muzzlePos, const idMat3& weaponAxis ); + virtual void WeaponFeedback(); + + void CalculateMuzzleRise( idVec3& origin, idMat3& axis ); + void UpdateMuzzleKick(); + + virtual void EjectBrass(); + virtual bool TransformBrass( const weaponJointHandle_t& handle, idVec3& origin, idMat3& axis ); + idStr GetScriptFunction() { return scriptFunction; } + const char * GetString( const char *key ) { return dict->GetString( key ); } + + ID_INLINE virtual bool HasAmmo() const; + virtual void UseAmmo(); + virtual int AmmoAvailable() const; + void AddToClip( int amount ); + static ammo_t GetAmmoType( const char *ammoname ); + ID_INLINE ammo_t GetAmmoType() const; + ID_INLINE int AmmoInClip() const; + ID_INLINE int ClipSize() const; + virtual bool UsesCrosshair() const; + ID_INLINE int LowAmmo() const; + + //rww - net friendliness + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void UpdateWeaponJoints(void); //rww + + virtual bool CheckThirdPersonMuzzle(idVec3 &origin, idMat3 &axis); //rww +protected: + void SetWeaponJointHandleList( const char* keyPrefix, hhCycleList& jointList ); + void SaveWeaponJointHandleList( const hhCycleList& jointList, idSaveGame *savefile ) const; + void RestoreWeaponJointHandleList( hhCycleList& jointList, idRestoreGame *savefile ); + + void SetBrassDict( const char* name ); + + virtual void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); + virtual idEntity* GetProjectileOwner() const; + virtual const idBounds& GetCollisionBBox(); + + virtual hhRenderEntity *GetSelf(); + virtual const hhRenderEntity *GetSelfConst() const; + +protected: + idEntityPtr self; + idEntityPtr owner; + + idStr scriptFunction; + idStr brassDefName; // HUMANHEAD mdl: Added to save/load brassDef dict + const idDict * brassDef; + int brassDelay; + + ammo_t ammoType; + int clipSize; // 0 means no reload + int ammoClip; + int lowAmmo; + + // weapon kick + int muzzle_kick_time; + int muzzle_kick_maxtime; + idAngles muzzle_kick_angles; + idVec3 muzzle_kick_offset; + + idVec2 aimDist; + + // joints from models + hhCycleList barrelJoints; + hhCycleList ejectJoints; +}; + +/* +================ +hhWeaponFireController::HasAmmo +================ +*/ +ID_INLINE bool hhWeaponFireController::HasAmmo() const { + return hhFireController::HasAmmo() && ( (ClipSize() == 0) || (AmmoInClip() > 0) ); +} + +/* +================ +hhWeaponFireController::GetAmmoType +================ +*/ +ammo_t hhWeaponFireController::GetAmmoType() const { + return ammoType; +} + +/* +================ +hhWeaponFireController::AmmoInClip +================ +*/ +int hhWeaponFireController::AmmoInClip() const { + return ammoClip; +} + +/* +================ +hhWeaponFireController::ClipSize +================ +*/ +int hhWeaponFireController::ClipSize() const { + return clipSize; +} + +int hhWeaponFireController::LowAmmo() const { + return lowAmmo; +} + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponhider.cpp b/neo/prey/Prey/prey_weaponhider.cpp new file mode 100644 index 00000000..57db8fce --- /dev/null +++ b/neo/prey/Prey/prey_weaponhider.cpp @@ -0,0 +1,95 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +#define FIRE_PREDICTION_TOLERANCE 0.16f //rww - time lag to allow prediction to handle fire and reload times + +CLASS_DECLARATION( hhWeaponFireController, hhHiderWeaponAltFireController ) +END_CLASS + +//============================================================================= +// +// hhHiderWeaponAltFireController::GetProjectileDict +// +//============================================================================= + +const idDict* hhHiderWeaponAltFireController::GetProjectileDict() const +{ + switch( self->AmmoInClip() ) { + case 1: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile1"), false ); + case 2: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile2"), false ); + case 3: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile3"), false ); + case 4: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile4"), false ); + default: return gameLocal.FindEntityDefDict( dict->GetString("def_projectile"), false ); + } +} + +CLASS_DECLARATION( hhWeapon, hhWeaponHider ) +END_CLASS + +hhWeaponHider::hhWeaponHider() { + nextPredictionAttack = 0.0f; + lastPredictionAttack = 0.0f; + nextPredictionTimeSkip = 0; +} + +void hhWeaponHider::ClientPredictionThink( void ) { //rww + hhWeapon::ClientPredictionThink(); + if (!gameLocal.isNewFrame) { + return; + } + if (owner.IsValid()) { + if (fabsf(WEAPON_NEXTATTACK-nextPredictionAttack) > FIRE_PREDICTION_TOLERANCE) { //allow some margin of error for prediction + if (owner->entityNumber == gameLocal.localClientNum) { + if (nextPredictionTimeSkip < gameLocal.time) { + if (nextPredictionTimeSkip) { + WEAPON_NEXTATTACK = nextPredictionAttack; + nextPredictionTimeSkip = 0; + } + else { + nextPredictionTimeSkip = gameLocal.time + 300; + } + } + } + else { + if (nextPredictionTimeSkip < gameLocal.time) { //for non-local clients, sync up if we go out of range on every new prediction frame + WEAPON_NEXTATTACK = nextPredictionAttack; + } + else { + owner->forcePredictionButtons |= BUTTON_ATTACK; + } + } + } + else if (owner->entityNumber == gameLocal.localClientNum) { + nextPredictionTimeSkip = 0; + } + } +} + +void hhWeaponHider::WriteToSnapshot( idBitMsgDelta &msg ) const { //rww + hhWeapon::WriteToSnapshot(msg); + + msg.WriteFloat(WEAPON_NEXTATTACK); +} +void hhWeaponHider::ReadFromSnapshot( const idBitMsgDelta &msg ) { //rww + hhWeapon::ReadFromSnapshot(msg); + + nextPredictionAttack = msg.ReadFloat(); + + if (owner.IsValid()) { + if (owner->entityNumber != gameLocal.localClientNum) { + if (fabsf(WEAPON_NEXTATTACK-nextPredictionAttack) > FIRE_PREDICTION_TOLERANCE) { //ensure this client "fires" his next prediction frame + owner->forcePredictionButtons |= BUTTON_ATTACK; + if (lastPredictionAttack != nextPredictionAttack) { + nextPredictionTimeSkip = gameLocal.time + 300; //give a few snapshots to straighten out for the local client + } + } + else { + owner->forcePredictionButtons &= ~BUTTON_ATTACK; + } + } + } + + lastPredictionAttack = nextPredictionAttack; +} diff --git a/neo/prey/Prey/prey_weaponhider.h b/neo/prey/Prey/prey_weaponhider.h new file mode 100644 index 00000000..89e85011 --- /dev/null +++ b/neo/prey/Prey/prey_weaponhider.h @@ -0,0 +1,42 @@ +#ifndef __HH_WEAPON_HIDER_H +#define __HH_WEAPON_HIDER_H + + +//============================================================================= +// +// hhHiderWeaponAltFireController +// +//============================================================================= +class hhHiderWeaponAltFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhHiderWeaponAltFireController); +public: + ID_INLINE int AmmoRequired() const; + virtual const idDict* GetProjectileDict() const; +}; + +ID_INLINE int hhHiderWeaponAltFireController::AmmoRequired() const { + return self->AmmoInClip(); +} + + +class hhWeaponHider : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponHider ); + + public: + hhWeaponHider(); + float nextPredictionAttack; //rww - does not need to be saved/restored, used only in client code. + float lastPredictionAttack; //rww - does not need to be saved/restored, used only in client code. + int nextPredictionTimeSkip; //rww - does not need to be saved/restored, used only in client code. + + protected: + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); + virtual void ClientPredictionThink( void ); //rww + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; //rww + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); //rww +}; + +ID_INLINE hhWeaponFireController* hhWeaponHider::CreateAltFireController() { + return new hhHiderWeaponAltFireController; +} + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponrifle.cpp b/neo/prey/Prey/prey_weaponrifle.cpp new file mode 100644 index 00000000..09b775d1 --- /dev/null +++ b/neo/prey/Prey/prey_weaponrifle.cpp @@ -0,0 +1,466 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponZoomable + +***********************************************************************/ +const idEventDef EV_Weapon_ZoomIn( "zoomIn" ); +const idEventDef EV_Weapon_ZoomOut( "zoomOut" ); + +CLASS_DECLARATION( hhWeapon, hhWeaponZoomable ) + EVENT( EV_Weapon_ZoomIn, hhWeaponZoomable::Event_ZoomIn ) + EVENT( EV_Weapon_ZoomOut, hhWeaponZoomable::Event_ZoomOut ) +END_CLASS + +/* +================ +hhWeaponZoomable::ZoomIn +================ +*/ +void hhWeaponZoomable::ZoomIn() { + if( owner.IsValid() && dict ) { + owner->GetZoomFov().Init( gameLocal.GetTime(), SEC2MS(dict->GetFloat("zoomDuration")), owner->CalcFov(true), GetZoomFov() ); + } + + clientZoomTime = gameLocal.time + CLIENT_ZOOM_FUDGE; //rww + + bZoomed = true; +} + +/* +================ +hhWeaponZoomable::ZoomOut +================ +*/ +void hhWeaponZoomable::ZoomOut() { + if( owner.IsValid() && dict ) { + owner->GetZoomFov().Init( gameLocal.GetTime(), SEC2MS(dict->GetFloat("zoomDuration")), owner->CalcFov(true), g_fov.GetInteger() ); + } + + clientZoomTime = gameLocal.time + CLIENT_ZOOM_FUDGE; //rww + + bZoomed = false; +} + +/* +================ +hhWeaponZoomable::ZoomIn +================ +*/ +void hhWeaponZoomable::ZoomInStep() { + int maxFov = spawnArgs.GetInt( "zoomFovMax" ); + if ( zoomFov < maxFov ) { + zoomFov += spawnArgs.GetInt( "zoomFovStep" ); + StartSound( "snd_scope_click", SND_CHANNEL_ANY, 0, false, NULL ); + if ( zoomFov > maxFov ) { + zoomFov = maxFov; + } + ZoomIn(); + } + bZoomed = true; + owner->inventory.zoomFov = zoomFov; +} + +/* +================ +hhWeaponZoomable::ZoomOut +================ +*/ +void hhWeaponZoomable::ZoomOutStep() { + int minFov = spawnArgs.GetInt( "zoomFovMin" ); + if ( zoomFov > minFov ) { + zoomFov -= spawnArgs.GetInt( "zoomFovStep" ); + StartSound( "snd_scope_click", SND_CHANNEL_ANY, 0, false, NULL ); + if ( zoomFov < minFov ) { + zoomFov = minFov; + } + ZoomIn(); + } + bZoomed = true; + owner->inventory.zoomFov = zoomFov; +} + +/* +================ +hhWeaponZoomable::Event_ZoomIn +================ +*/ +void hhWeaponZoomable::Event_ZoomIn() { + ZoomIn(); +} + +/* +================ +hhWeaponZoomable::Event_ZoomOut +================ +*/ +void hhWeaponZoomable::Event_ZoomOut() { + ZoomOut(); +} + +/* +================ +hhWeaponZoomable::WriteToSnapshot +================ +*/ +void hhWeaponZoomable::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeapon::WriteToSnapshot(msg); + + msg.WriteBits(renderEntity.suppressSurfaceInViewID, GENTITYNUM_BITS); //if suppressSurfaceInViewID were to equal the last ent+1 this would be bad + //but, since it should never be world/none, this should be fine. + msg.WriteBits(bZoomed, 1); + msg.WriteBits(WEAPON_ALTMODE, 1); + + msg.WriteBits(IsHidden(), 1); +} + +/* +================ +hhWeaponZoomable::ReadFromSnapshot +================ +*/ +void hhWeaponZoomable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeapon::ReadFromSnapshot(msg); + + int suppressSurfInViewID = msg.ReadBits(GENTITYNUM_BITS); + bool zoomed = !!msg.ReadBits(1); + bool weaponAltMode = !!msg.ReadBits(1); + bool hidden = !!msg.ReadBits(1); + if (clientZoomTime < gameLocal.time) { + renderEntity.suppressSurfaceInViewID = suppressSurfInViewID; + bZoomed = zoomed; + WEAPON_ALTMODE = weaponAltMode; + if (hidden != IsHidden()) { + if (hidden) { + Hide(); + } + else { + Show(); + } + } + } +} + +/* +================ +hhWeaponZoomable::Save +================ +*/ +void hhWeaponZoomable::Save( idSaveGame *savefile ) const { + savefile->WriteBool( bZoomed ); +} + +/* +================ +hhWeaponZoomable::Restore +================ +*/ +void hhWeaponZoomable::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( bZoomed ); +} + +/*********************************************************************** + + hhWeaponRifle + +***********************************************************************/ +//This probably should be on entity but since no body else needs it I'll leave it here +const idEventDef EV_SuppressSurfaceInViewID( "", "d" ); + +CLASS_DECLARATION( hhWeaponZoomable, hhWeaponRifle ) + EVENT( EV_SuppressSurfaceInViewID, hhWeaponRifle::Event_SuppressSurfaceInViewID ) +END_CLASS + +/* +================ +hhWeaponRifle::Spawn +================ +*/ +void hhWeaponRifle::Spawn(void) { + zoomOverlayGui = NULL; + clientZoomTime = 0; //rww - no need to save/restore this + fl.clientEvents = true; //rww - for "prediction" +} + +/* +================ +hhWeaponRifle::~hhWeaponRifle +================ +*/ +hhWeaponRifle::~hhWeaponRifle(void) { + // Ensure that the scope view is disabled anytime the rifle goes away + ZoomOut(); + + zoomOverlayGui = NULL; + + SAFE_REMOVE( laserSight ); +} + +/* +================ +hhWeaponRifle::ParseDef +================ +*/ +void hhWeaponRifle::ParseDef( const char* objectname ) { + hhWeaponZoomable::ParseDef( objectname ); + + SAFE_REMOVE(laserSight); + + //rww - client-server-localized. + laserSight = static_cast( gameLocal.SpawnClientObject(dict->GetString("beam_laserSight"), NULL) ); + if ( laserSight.IsValid() ) { // cjr - Don't allow the laser show up in the player's zoomed view + assert(owner.IsValid()); + laserSight->GetRenderEntity()->suppressSurfaceInViewID = owner->entityNumber + 1; + laserSight->fl.neverDormant = true; + laserSight->snapshotOwner = this; //rww + } + + DeactivateLaserSight(); + + if( owner->inventory.zoomFov ) + zoomFov = owner->inventory.zoomFov; + WEAPON_ALTMODE = false; +} + +/* +================ +hhWeaponRifle::Ticker +================ +*/ +void hhWeaponRifle::Ticker() { + trace_t trace; + idVec3 start; + idMat3 axis; + + if( laserSight.IsValid() && laserSight->IsActive() ) { + GetJointWorldTransform( dict->GetString("joint_laserSight"), start, axis ); + gameLocal.clip.TracePoint( trace, start, start + GetAxis()[0] * CM_MAX_TRACE_DIST, MASK_VISIBILITY, owner.GetEntity() ); + laserSight->SetOrigin( start ); + laserSight->SetTargetLocation( trace.endpos ); + } + + hhWeaponZoomable::Ticker(); +} + +/* +================ +hhWeaponRifle::ZoomIn +================ +*/ +void hhWeaponRifle::ZoomIn() { + CancelEvents( &EV_SuppressSurfaceInViewID ); + + bool alreadyZoomed = bZoomed; + + hhWeaponZoomable::ZoomIn(); + + SetViewAnglesSensitivity( owner->GetZoomFov().GetEndValue() ); + + ProcessEvent( &EV_SuppressSurfaceInViewID, owner->entityNumber + 1 ); + RestoreGUI( "gui_zoomOverlay", &zoomOverlayGui ); + + if (zoomOverlayGui && !alreadyZoomed) { + zoomOverlayGui->HandleNamedEvent("zoomIn"); + + // Display Tips + if (!gameLocal.isMultiplayer && g_tips.GetBool()) { + gameLocal.SetTip(zoomOverlayGui, "_impulse15", "#str_41156", NULL, NULL, "tip1"); + gameLocal.SetTip(zoomOverlayGui, "_impulse14", "#str_41157", NULL, NULL, "tip2"); + zoomOverlayGui->HandleNamedEvent( "tipWindowUp" ); + } + } + + ActivateLaserSight(); + + if (owner.IsValid() && owner.GetEntity()) { + if (owner->entityNumber == gameLocal.localClientNum) { + renderSystem->SetScopeView( true ); + } + owner->bScopeView = true; + } +} + +/* +================ +hhWeaponRifle::ZoomOut +================ +*/ +void hhWeaponRifle::ZoomOut() { + CancelEvents( &EV_SuppressSurfaceInViewID ); + + bool wasZoomed = bZoomed; + + hhWeaponZoomable::ZoomOut(); + + if (owner.IsValid() && owner.GetEntity()) { //rww - this seems to be possible when a player disconnects while zoomed i think + SetViewAnglesSensitivity( owner->GetZoomFov().GetEndValue() ); + } + + // Remove tips + if (zoomOverlayGui) { + zoomOverlayGui->HandleNamedEvent( "tipWindowDown" ); + } + + if ( dict ) { + PostEventMS( &EV_SuppressSurfaceInViewID, SEC2MS(dict->GetFloat("zoomDuration")) * 0.5f, 0 ); + } + zoomOverlayGui = NULL; + + if( wasZoomed ) { + SetShaderParm(7, -MS2SEC(gameLocal.time)); + } + + DeactivateLaserSight(); + + if (owner.IsValid() && owner.GetEntity()) { + if (owner->entityNumber == gameLocal.localClientNum) { + renderSystem->SetScopeView( false ); // CJR: Disable special render scope view + } + owner->bScopeView = false; + } +} + +/* +================ +hhWeaponRifle::ActivateLaserSight +================ +*/ +void hhWeaponRifle::ActivateLaserSight() { + if( laserSight.IsValid() ) { + laserSight->Activate( true ); + BecomeActive( TH_TICKER ); + } +} + +/* +================ +hhWeaponRifle::DeactivateLaserSight +================ +*/ +void hhWeaponRifle::DeactivateLaserSight() { + if( laserSight.IsValid() ) { + laserSight->Activate( false ); + BecomeInactive( TH_TICKER ); + } +} + +/* +================ +hhWeaponRifle::UpdateGUI +================ +*/ +void hhWeaponRifle::UpdateGUI() { + float parmValue = 0.0f; + idAngles angles; + + if( zoomOverlayGui ) {//This is for the sniper + float minFov = spawnArgs.GetFloat( "zoomFovMin" ); + float maxFov = spawnArgs.GetFloat( "zoomFovMax" ); + parmValue = idMath::ClampFloat(0.3f, 1.0f, 1.0f - 0.7f*(zoomFov - minFov)/(maxFov - minFov)); + zoomOverlayGui->SetStateFloat( "fov", parmValue ); + + angles = GetAxis().ToAngles(); + parmValue = angles.Normalize360().yaw; + zoomOverlayGui->SetStateFloat( "yaw", parmValue ); + zoomOverlayGui->StateChanged( gameLocal.time ); + zoomOverlayGui->Redraw( gameLocal.GetTime() ); + } +} + +/* +================ +hhWeaponRifle::Event_SuppressSurfaceInViewID +================ +*/ +void hhWeaponRifle::Event_SuppressSurfaceInViewID( const int id ) { + renderEntity.suppressSurfaceInViewID = id; +} + +/* +================ +hhWeaponRifle::Save +================ +*/ +void hhWeaponRifle::Save( idSaveGame *savefile ) const { + laserSight.Save( savefile ); + savefile->WriteUserInterface( zoomOverlayGui, false ); +} + +/* +================ +hhWeaponRifle::Restore +================ +*/ +void hhWeaponRifle::Restore( idRestoreGame *savefile ) { + laserSight.Restore( savefile ); + savefile->ReadUserInterface( zoomOverlayGui ); +} + +/* +================ +hhWeaponRifle::WriteToSnapshot +================ +*/ +void hhWeaponRifle::WriteToSnapshot( idBitMsgDelta &msg ) const { + hhWeaponZoomable::WriteToSnapshot(msg); + + bool laserSightActive = (laserSight.IsValid() && IsActive(TH_TICKER)); + msg.WriteBits(laserSightActive, 1); + + if (zoomOverlayGui) { + msg.WriteBits(1, 1); + } + else { + msg.WriteBits(0, 1); + } +} + +/* +================ +hhWeaponRifle::ReadFromSnapshot +================ +*/ +void hhWeaponRifle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hhWeaponZoomable::ReadFromSnapshot(msg); + + bool laserSightActive = !!msg.ReadBits(1); + if (laserSight.IsValid() && IsActive(TH_TICKER) != laserSightActive) { + if (laserSightActive) { + ActivateLaserSight(); + } + else { + DeactivateLaserSight(); + } + } + + bool guiActive = !!msg.ReadBits(1); + if (clientZoomTime < gameLocal.time) { + if ((!!zoomOverlayGui) != guiActive) { + if (guiActive) { + if (!zoomOverlayGui) { + RestoreGUI( "gui_zoomOverlay", &zoomOverlayGui ); + if (zoomOverlayGui) { + zoomOverlayGui->HandleNamedEvent("zoomIn"); + } + } + } + else { + if (zoomOverlayGui) { + zoomOverlayGui = NULL; + } + } + } + } +} + +CLASS_DECLARATION( hhWeaponFireController, hhSniperRifleFireController ) +END_CLASS + +void hhSniperRifleFireController::CalculateMuzzlePosition( idVec3& origin, idMat3& axis ) { + origin = owner->GetEyePosition(); + axis = self->GetAxis(); +} \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponrifle.h b/neo/prey/Prey/prey_weaponrifle.h new file mode 100644 index 00000000..1ffd3867 --- /dev/null +++ b/neo/prey/Prey/prey_weaponrifle.h @@ -0,0 +1,91 @@ +#ifndef __HH_WEAPON_RIFLE_H +#define __HH_WEAPON_RIFLE_H + +#define CLIENT_ZOOM_FUDGE 100 + +/*********************************************************************** + + hhWeaponZoomable + +***********************************************************************/ +class hhWeaponZoomable : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponZoomable ); + + public: + hhWeaponZoomable() : hhWeapon(), bZoomed( false ) { } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + ID_INLINE bool IsZoomed() const { return bZoomed; } + virtual void ZoomInStep(); + virtual void ZoomOutStep(); + + void Event_ZoomIn(); + void Event_ZoomOut(); + protected: + virtual void ZoomIn(); + virtual void ZoomOut(); + protected: + bool bZoomed; + + public: + int clientZoomTime; //rww - a bit hacky, to prevent jittering in prediction +}; + +/*********************************************************************** + + hhWeaponRifle + +***********************************************************************/ +class hhWeaponRifle : public hhWeaponZoomable { + CLASS_PROTOTYPE( hhWeaponRifle ); + + public: + void Spawn(); + virtual ~hhWeaponRifle(); + + virtual void UpdateGUI(); + + virtual void Ticker(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void ZoomIn(); + virtual void ZoomOut(); + + protected: + virtual void ParseDef( const char* objectname ); + + virtual void ActivateLaserSight(); + virtual void DeactivateLaserSight(); + + protected: + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); + void Event_SuppressSurfaceInViewID( const int id ); + + protected: + idUserInterface* zoomOverlayGui; + idEntityPtr laserSight; +}; + +class hhSniperRifleFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhSniperRifleFireController); +public: + void CalculateMuzzlePosition( idVec3& origin, idMat3& axis ); +}; + +ID_INLINE hhWeaponFireController* hhWeaponRifle::CreateAltFireController() { + return new hhSniperRifleFireController; +} + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponrocketlauncher.cpp b/neo/prey/Prey/prey_weaponrocketlauncher.cpp new file mode 100644 index 00000000..e7904704 --- /dev/null +++ b/neo/prey/Prey/prey_weaponrocketlauncher.cpp @@ -0,0 +1,29 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhWeaponRocketLauncherFireController + +***********************************************************************/ +CLASS_DECLARATION( hhWeaponFireController, hhRocketLauncherFireController ) +END_CLASS + +/*********************************************************************** + + hhWeaponRocketLauncherAltFireController + +***********************************************************************/ +CLASS_DECLARATION( hhRocketLauncherFireController, hhRocketLauncherAltFireController ) +END_CLASS + +/*********************************************************************** + + hhWeaponRocketLauncher + +***********************************************************************/ +CLASS_DECLARATION( hhWeapon, hhWeaponRocketLauncher ) +END_CLASS + diff --git a/neo/prey/Prey/prey_weaponrocketlauncher.h b/neo/prey/Prey/prey_weaponrocketlauncher.h new file mode 100644 index 00000000..088bc3c0 --- /dev/null +++ b/neo/prey/Prey/prey_weaponrocketlauncher.h @@ -0,0 +1,97 @@ +#ifndef __HH_WEAPON_ROCKETLAUNCHER_H +#define __HH_WEAPON_ROCKETLAUNCHER_H + +/*********************************************************************** + + hhRocketLauncherFireController + +***********************************************************************/ +class hhRocketLauncherFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhRocketLauncherFireController) + + public: + ID_INLINE virtual void EjectBrass(); + + protected: + ID_INLINE virtual bool TransformBrass( const weaponJointHandle_t& handle, idVec3 &origin, idMat3 &axis ); +}; + +/* +================ +hhRocketLauncherFireController::EjectBrass +================ +*/ +ID_INLINE void hhRocketLauncherFireController::EjectBrass() { + for( int ix = ejectJoints.Num() - 1; ix >= 0; --ix ) { + hhWeaponFireController::EjectBrass(); + } +} + +/* +================ +hhRocketLauncherFireController::TransformBrass +================ +*/ +ID_INLINE bool hhRocketLauncherFireController::TransformBrass( const weaponJointHandle_t& handle, idVec3 &origin, idMat3 &axis ) { + return self->GetJointWorldTransform( handle, origin, axis ); +} + +/*********************************************************************** + + hhRocketLauncherAltFireController + +***********************************************************************/ +class hhRocketLauncherAltFireController : public hhRocketLauncherFireController { + CLASS_PROTOTYPE(hhRocketLauncherAltFireController) + + protected: + ID_INLINE virtual idMat3 DetermineProjectileAxis( const idMat3& muzzleAxis ); +}; + +/* +================ +hhRocketLauncherAltFireController::DetermineProjectileAxis +================ +*/ +ID_INLINE idMat3 hhRocketLauncherAltFireController::DetermineProjectileAxis( const idMat3& muzzleAxis ) { + float ang = spread * hhMath::Sqrt( gameLocal.random.RandomFloat() ); + float yaw = DEG2RAD(yawSpread); + float spin = hhMath::TWO_PI * gameLocal.random.RandomFloat(); + idVec3 dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( hhMath::Sin(ang) * hhMath::Sin(spin) ) - muzzleAxis[ 1 ] * ( hhMath::Sin(ang+yaw) * hhMath::Cos(spin) ); + dir.Normalize(); + + return dir.ToMat3(); +} + +/*********************************************************************** + + hhWeaponRocketLauncher + +***********************************************************************/ +class hhWeaponRocketLauncher : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponRocketLauncher ); + + protected: + ID_INLINE virtual hhWeaponFireController* CreateFireController(); + ID_INLINE virtual hhWeaponFireController* CreateAltFireController(); +}; + +/* +================ +hhWeaponRocketLauncher::CreateFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeaponRocketLauncher::CreateFireController() { + return new hhRocketLauncherFireController; +} + +/* +================ +hhWeaponRocketLauncher::CreateAltFireController +================ +*/ +ID_INLINE hhWeaponFireController* hhWeaponRocketLauncher::CreateAltFireController() { + return new hhRocketLauncherAltFireController; +} + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponsoulstripper.cpp b/neo/prey/Prey/prey_weaponsoulstripper.cpp new file mode 100644 index 00000000..ccbf2bca --- /dev/null +++ b/neo/prey/Prey/prey_weaponsoulstripper.cpp @@ -0,0 +1,1427 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +const idEventDef EV_KillBeam( "killBeam", NULL, NULL ); + +CLASS_DECLARATION( hhWeaponFireController, hhSoulStripperAltFireController ) +END_CLASS + +/*********************************************************************** + + hhSoulStripperAltFireController::LaunchProjectiles + +***********************************************************************/ +void hhSoulStripperAltFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + // Spawn minion + idDict args; + + idVec3 minionOffset = dict->GetVector( "minionOffset" ); + args.SetVector( "origin", launchOrigin + aimAxis * minionOffset ); + args.SetMatrix( "rotation", aimAxis ); + const char *minionDef = dict->GetString( "def_minion" ); + gameLocal.SpawnObject( minionDef, &args ); + + // Spawn blast effect + hhFxInfo fxInfo; + fxInfo.SetNormal( aimAxis[0] ); + fxInfo.RemoveWhenDone( true ); + self->BroadcastFxInfo( dict->GetString( "fx_blastflash" ), launchOrigin, aimAxis, &fxInfo ); +} + +CLASS_DECLARATION( hhWeaponFireController, hhBeamBasedFireController ) +END_CLASS + +/*********************************************************************** + + hhBeamBasedFireController::LaunchProjectiles + +***********************************************************************/ +void hhBeamBasedFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + idVec3 start, end; + trace_t results; + + float traceLength; + + idVec3 boneOrigin; + idMat3 boneAxis; + + if ( projTime < gameLocal.time ) { + hhFireController::LaunchProjectiles(launchOrigin, aimAxis, pushVelocity, projOwner); + projTime = gameLocal.time + dict->GetInt("projRate", "100"); + } + + start = launchOrigin; + + traceLength = dict->GetFloat("traceLength","100"); + + end = start + traceLength * aimAxis[0]; + + // Check to see if the beam should attempt to hit something + if (owner.IsValid()) { + //rww - can hit owners due to different angles in mp (although not the local client since the trace is done on the client every frame) + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + } + else { + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, NULL ); + } + + if ( !shotbeam.IsValid() ) { + shotbeam = hhBeamSystem::SpawnBeam( launchOrigin, dict->GetString( "beam" ), aimAxis, true ); + if( !shotbeam.IsValid() ) + return; + + if (self->GetOwner()) { + shotbeam->snapshotOwner = self->GetOwner(); //rww - this beam is client-local, but will remove itself when the gun is not actively in the snapshot (meaning, it's not actively updated) + } + } + + shotbeam->Activate( true ); + + hhFxInfo fxInfo; + fxInfo.SetNormal( results.c.normal ); + fxInfo.RemoveWhenDone( true ); + + if( !impactFx.IsValid() ) { + const char* str=dict->GetString("fx_impact"); + if ( str && str[0] ) + impactFx=self.GetEntity()->SpawnFxLocal( str, results.endpos, results.c.normal.ToMat3(), &fxInfo, true ); + } + + if( impactFx.IsValid() ) { + if (self.IsValid() && self->GetOwner()) { + impactFx->snapshotOwner = self->GetOwner(); //rww + } + static_cast( impactFx.GetEntity() )->SetFxInfo(fxInfo); + impactFx->Nozzle(true); + impactFx->SetOrigin(results.endpos-aimAxis[0]*20); + } + + idVec3 beamOrigin; + idMat3 beamAxis; + CalculateMuzzlePosition( beamOrigin, beamAxis ); // Must use this instead of launchOrigin, since launch origin is forced into the player's bbox + + if (gameLocal.isMultiplayer) { //rww - the visual origin of the beam needs to be from the player's world weapon joint to avoid the optic blast + idVec3 tpOrigin; + idMat3 tpAxis; + if (CheckThirdPersonMuzzle(tpOrigin, tpAxis)) { + beamOrigin = tpOrigin; + //just keep the existing axis + } + } + shotbeam->SetOrigin( beamOrigin ); + shotbeam->SetAxis( aimAxis ); + shotbeam->SetTargetLocation( results.endpos-aimAxis[0]*40 ); + float length = (results.endpos - start).Length(); + shotbeam->SetBeamOffsetScale( length/150.0f ); + + GetSelf()->CancelEvents( &EV_KillBeam ); + GetSelf()->PostEventSec( &EV_KillBeam, dict->GetFloat( "beamTime", "0.1" ) ); +} + +void hhBeamBasedFireController::KillBeam() +{ + if( impactFx.IsValid() ) { + impactFx->Nozzle(false); + if (gameLocal.isClient) { + impactFx->snapshotOwner = NULL; //rww - prevent it from being unhidden between now and the removal frame on the client + } + } + + if( shotbeam.IsValid() ) + shotbeam->Activate( false ); +} + +void hhBeamBasedFireController::Think() +{ + if( shotbeam.IsValid() ) { + idVec3 beamOrigin; + idMat3 beamAxis; + CalculateMuzzlePosition( beamOrigin, beamAxis ); // Must use this instead of launchOrigin, since launch origin is forced into the player's bbox + idVec3 adjustedOrigin = AssureInsideCollisionBBox( beamOrigin, GetSelf()->GetAxis(), GetCollisionBBox(), projectileMaxHalfDimension ); + idMat3 aimAxis = DetermineAimAxis( adjustedOrigin, GetSelf()->GetAxis() ); + + if (gameLocal.isMultiplayer) { //rww - the visual origin of the beam needs to be from the player's world weapon joint to avoid the optic blast + idVec3 tpOrigin; + idMat3 tpAxis; + if (CheckThirdPersonMuzzle(tpOrigin, tpAxis)) { + beamOrigin = tpOrigin; + //just keep the existing axis + } + } + shotbeam->SetOrigin( beamOrigin ); + shotbeam->SetAxis( aimAxis ); + } +} + +/*********************************************************************** + + hhBeamBasedFireController::Init + +***********************************************************************/ +void hhBeamBasedFireController::Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ) +{ + hhWeaponFireController::Init( viewDict, self, owner ); + + shotbeam = NULL; + projTime = 0; +} + +/*********************************************************************** + + hhBeamBasedFireController::~hhBeamBasedFireController + +***********************************************************************/ +hhBeamBasedFireController::~hhBeamBasedFireController() +{ + KillBeam(); + if( impactFx.IsValid() ) + SAFE_REMOVE(impactFx); + if( shotbeam.IsValid() ) + SAFE_REMOVE(shotbeam); +} + +void hhBeamBasedFireController::Save( idSaveGame *savefile ) const { + shotbeam.Save( savefile ); + savefile->WriteInt( projTime ); + impactFx.Save( savefile ); +} + +void hhBeamBasedFireController::Restore( idRestoreGame *savefile ) { + shotbeam.Restore( savefile ); + savefile->ReadInt( projTime ); + impactFx.Restore( savefile ); +} + +CLASS_DECLARATION( hhBeamBasedFireController, hhSunbeamFireController ) +END_CLASS + +void hhSunbeamFireController::LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ) { + hhBeamBasedFireController::LaunchProjectiles(launchOrigin, aimAxis, pushVelocity, projOwner); + //HUMANHEAD PCF rww 05/18/06 - since values are assuming 60hz, make them relative + float backpush = dict->GetFloat("backpush","0.0")*(60.0f/(float)USERCMD_HZ); + float slowwalk = dict->GetFloat("slowwalk","0.0")*(60.0f/(float)USERCMD_HZ); + int knockback = dict->GetInt("knockback","0")*(60/USERCMD_HZ); + //HUMANHEAD END + + hhPhysics_Player* pp = static_cast(owner->GetPlayerPhysics()); + idVec3 v = pp->GetLinearVelocity()+owner->GetAxis()[0]*backpush; + float f = pp->GetLinearVelocity()*owner->GetAxis()[0]; + v += owner->GetAxis()[0] * ( (f>0) ? slowwalk*f : 0 ); + pp->SetLinearVelocity(v); + pp->SetKnockBack(knockback); +} + +void hhSunbeamFireController::Think() +{ + idVec3 start, end; + + if( shotbeam.IsValid() ) { + start = shotbeam->GetOrigin(); + end = shotbeam->GetTargetLocation(); + float length = (end - start).Length(); + + idVec3 beamOrigin; + idMat3 beamAxis; + CalculateMuzzlePosition( beamOrigin, beamAxis ); // Must use this instead of launchOrigin, since launch origin is forced into the player's bbox + idVec3 adjustedOrigin = AssureInsideCollisionBBox( beamOrigin, GetSelf()->GetAxis(), GetCollisionBBox(), projectileMaxHalfDimension ); + idMat3 aimAxis = DetermineAimAxis( adjustedOrigin, GetSelf()->GetAxis() ); + end = beamOrigin + length * aimAxis[0]; + + if (gameLocal.isMultiplayer) { //rww - the visual origin of the beam needs to be from the player's world weapon joint to avoid the optic blast + idVec3 tpOrigin; + idMat3 tpAxis; + if (CheckThirdPersonMuzzle(tpOrigin, tpAxis)) { + beamOrigin = tpOrigin; + //just keep the existing axis + } + } + shotbeam->SetOrigin( beamOrigin ); + shotbeam->SetAxis( aimAxis ); + shotbeam->SetTargetLocation( end ); + + if( impactFx.IsValid() ) + impactFx->SetOrigin( end ); + } +} + +CLASS_DECLARATION( hhWeaponFireController, hhPlasmaFireController ) +END_CLASS + +//============================================================================= +// +// hhPlasmaFireController::LaunchProjectiles +// +//============================================================================= +bool hhPlasmaFireController::LaunchProjectiles( const idVec3& pushVelocity ) { + char* projStr = "projectile_plasma"; + + fireDelay = dict->GetFloat( "fireRate" ); + spread = DEG2RAD( dict->GetFloat("spread") ); + yawSpread = dict->GetFloat("yawSpread"); + + usercmd_t cmd; + idAngles temp; + owner->GetPilotInput(cmd,temp); + + idVec3 dir(cmd.forwardmove, -cmd.rightmove, cmd.upmove); + + if( dir.x > 0 ) { + projStr = "projectile_plasmaf"; + fireDelay = 0.1f; + } + + if( dir.x < 0 ) { + spread = DEG2RAD(10); + fireDelay = 0.08f; + projStr = "projectile_plasmab"; + } + + if( dir.y != 0 ) { + spread *= 0.4f; + yawSpread +=8; + } + + if( dir.z < 0 ) { //crouch + spread = DEG2RAD(0); + fireDelay = 1.0f; + projStr = "projectile_plasmac"; + } + + if( !owner->AI_ONGROUND ) { //jump + fireDelay = 0.06f; + projStr = "projectile_plasmaj"; + } + + projectile = gameLocal.FindEntityDefDict( projStr, false ); + + return hhWeaponFireController::LaunchProjectiles( pushVelocity ); +} + +/*********************************************************************** + + hhWeaponSoulStripper + +***********************************************************************/ + +//const idEventDef EV_DamageBeamTarget( "", NULL ); +const idEventDef EV_GetAnimPostFix( "getAnimPostfix", "", 's' ); +const idEventDef EV_Leech( "leech", NULL, 'd' ); +const idEventDef EV_EndLeech( "endLeech", NULL, 'f' ); +const idEventDef EV_LightFadeIn( "", "f" ); +const idEventDef EV_LightFadeOut( "", "f" ); +const idEventDef EV_LightFade( "lightFade", "fff" ); +const idEventDef EV_GetFireFunction( "getFireFunction", NULL, 's' ); + +CLASS_DECLARATION( hhWeapon, hhWeaponSoulStripper ) + EVENT( EV_PostSpawn, hhWeaponSoulStripper::Event_PostSpawn ) + EVENT( EV_GetAnimPostFix, hhWeaponSoulStripper::Event_GetAnimPostFix ) + EVENT( EV_Leech, hhWeaponSoulStripper::Event_Leech ) + EVENT( EV_EndLeech, hhWeaponSoulStripper::Event_EndLeech ) + EVENT( AI_PlayAnim, hhWeaponSoulStripper::Event_PlayAnim ) + EVENT( AI_PlayCycle, hhWeaponSoulStripper::Event_PlayCycle ) + EVENT( EV_LightFadeIn, hhWeaponSoulStripper::Event_LightFadeIn ) + EVENT( EV_LightFadeOut, hhWeaponSoulStripper::Event_LightFadeOut ) + EVENT( EV_LightFade, hhWeaponSoulStripper::Event_LightFade ) + EVENT( EV_GetFireFunction, hhWeaponSoulStripper::Event_GetFireFunction ) + EVENT( EV_KillBeam, hhWeaponSoulStripper::Event_KillBeam ) +END_CLASS + +//============================================================================= +// +// hhWeaponSoulStripper::hhWeaponSoulStripper +// +//============================================================================= + +hhWeaponSoulStripper::hhWeaponSoulStripper() { + // Initialize SFX on the weapon + beam = NULL; // Beam is properly spawned in PostSpawn + beamCanA1 = NULL; // Beam is properly spawned in PostSpawn + beamCanB1 = NULL; // Beam is properly spawned in PostSpawn + beamCanC1 = NULL; // Beam is properly spawned in PostSpawn + beamCanA2 = NULL; // Beam is properly spawned in PostSpawn + beamCanB2 = NULL; // Beam is properly spawned in PostSpawn + beamCanC2 = NULL; // Beam is properly spawned in PostSpawn + beamCanA3 = NULL; // Beam is properly spawned in PostSpawn + beamCanB3 = NULL; // Beam is properly spawned in PostSpawn + beamCanC3 = NULL; // Beam is properly spawned in PostSpawn + cansValid = false; + + fxCanA = NULL; + fxCanB = NULL; + fxCanC = NULL; + + targetTime = 0; + + fcDeclNum = 0; + + netInitialized = false; + //HUMANHEAD PCF mdl 05/04/06 - Initialize beamLightHandle here + beamLightHandle = -1; +} + +//============================================================================= +// +// hhWeaponSoulStripper::Spawn +// +//============================================================================= + +void hhWeaponSoulStripper::Spawn() { + BecomeActive( TH_TICKER ); + fl.clientEvents = true; //rww +} + +//============================================================================= +// +// hhWeaponSoulStripper::ParseDef +// +//============================================================================= + +void hhWeaponSoulStripper::ParseDef( const char *objectname ) { + hhWeapon::ParseDef( objectname ); + + maxBeamLength = dict->GetFloat( "maxBeamLength", "1024" ); + + // Initialize weapon logic + targetNode = NULL; + beamLength = 0.0f; + + if (gameLocal.isMultiplayer) + { //rww - then do postspawn now. + Event_PostSpawn(); + } + else + { // Spawn the beams and SFX + PostEventMS( &EV_PostSpawn, 0 ); + } +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_PostSpawn +// +//============================================================================= + +void hhWeaponSoulStripper::Event_PostSpawn() { //rww - note that this function can get called more than once in a leechgun's life time in mp on the client. + idVec3 boneOrigin; + idMat3 boneAxis; + + GetJointWorldTransform( dict->GetString("attach_beam"), boneOrigin, boneAxis); + + if (!beam.IsValid()) { + beam = hhBeamSystem::SpawnBeam( boneOrigin, dict->GetString( "beam_stripper" ), boneAxis, true ); + if ( beam.IsValid() ) { + beam->Activate( false ); + } + } + DestroyCans(); + SpawnCans(); + + // Spawn beam light + memset( &beamLight, 0, sizeof( beamLight ) ); + beamLight.lightId = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; + + beamLight.origin = GetOrigin(); + beamLight.axis = GetAxis(); + beamLight.pointLight = true; + beamLight.shader = declManager->FindMaterial( dict->GetString( "mtr_light" ) ); + beamLight.shaderParms[ SHADERPARM_RED ] = 1.0f; + beamLight.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + beamLight.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + beamLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + beamLight.lightRadius[0] = 115; + beamLight.lightRadius[1] = 115; + beamLight.lightRadius[2] = 1; + + //HUMANHEAD PCF mdl 05/04/06 - Moved this to the constructor + //beamLightHandle = -1; + + const idDict *energyDef; + const char* str = owner->inventory.energyType; + if ( !str || !str[0] ) + return; + + energyDef = gameLocal.FindEntityDefDict( str ); + if ( !energyDef ) + return; + + if (gameLocal.isClient) { //rww - do the minimal fireController update for the client here. + const idDeclEntityDef *fcDecl = gameLocal.FindEntityDef( energyDef->GetString("def_fireInfo"), false ); + if (fcDecl) { + const char *fcSpawnClass = fcDecl->dict.GetString("spawnclass", NULL); + if (!fcSpawnClass || !fcSpawnClass[0]) { + fcSpawnClass = "hhWeaponFireController"; //default to regular fire controller + } + idTypeInfo *cls = idClass::GetClass(fcSpawnClass); + if (cls) { + ClientUpdateFC(cls->typeNum, fcDecl->Index()); + } + } + } + else { + GiveEnergy( energyDef->GetString("def_fireInfo"), false ); + } +} + +void hhWeaponSoulStripper::CheckCans(void) { + bool wantCans = true; + if (owner.IsValid() && owner->entityNumber != gameLocal.localClientNum) { //rww - we don't need cannister fx unless we are the client that owns this weapon. (or spectating him) + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != owner->entityNumber ) { + wantCans = false; + } + } else { + wantCans = false; + } + } + + if (wantCans != cansValid) { + DestroyCans(); + if (wantCans) { + SpawnCans(); + } + } +} + +void hhWeaponSoulStripper::SpawnCans() { + if (owner.IsValid() && owner->entityNumber != gameLocal.localClientNum) { //rww - we don't need cannister fx unless we are the client that owns this weapon. (or spectating him) + if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); + if ( !p->spectating || p->spectator != owner->entityNumber ) { + return; + } + } else { + return; + } + } + + // Spawn individual canister beams (top to center) + beamCanA1 = SpawnCanisterBeam( "attach_topA", "attach_soulA", beam_canTop ); + beamCanB1 = SpawnCanisterBeam( "attach_topB", "attach_soulB", beam_canTop ); + beamCanC1 = SpawnCanisterBeam( "attach_topC", "attach_soulC", beam_canTop ); + + // Spawn individual canister beams (bottom to center) + beamCanA2 = SpawnCanisterBeam( "attach_bottomA", "attach_soulA", beam_canBot ); + beamCanB2 = SpawnCanisterBeam( "attach_bottomB", "attach_soulB", beam_canBot ); + beamCanC2 = SpawnCanisterBeam( "attach_bottomC", "attach_soulC", beam_canBot ); + + // Spawn individual canister beams (faint bottom to top glow) + beamCanA3 = SpawnCanisterBeam( "attach_bottomA", "attach_topA", beam_canGlow ); + beamCanB3 = SpawnCanisterBeam( "attach_bottomB", "attach_topB", beam_canGlow ); + beamCanC3 = SpawnCanisterBeam( "attach_bottomC", "attach_topC", beam_canGlow ); + + fxCanA = SpawnCanisterFx( "attach_bottomA", fx_can ); + fxCanB = SpawnCanisterFx( "attach_bottomB", fx_can ); + fxCanC = SpawnCanisterFx( "attach_bottomC", fx_can ); + + cansValid = true; +} + +void hhWeaponSoulStripper::DestroyCans() { + SAFE_REMOVE( beamCanA1 ); + SAFE_REMOVE( beamCanB1 ); + SAFE_REMOVE( beamCanC1 ); + + SAFE_REMOVE( beamCanA2 ); + SAFE_REMOVE( beamCanB2 ); + SAFE_REMOVE( beamCanC2 ); + + SAFE_REMOVE( beamCanA3 ); + SAFE_REMOVE( beamCanB3 ); + SAFE_REMOVE( beamCanC3 ); + + SAFE_REMOVE( fxCanA ); + SAFE_REMOVE( fxCanB ); + SAFE_REMOVE( fxCanC ); + + cansValid = false; +} + +//============================================================================= +// +// hhWeaponSoulStripper::SpawnCanisterBeam +// +//============================================================================= + +hhBeamSystem *hhWeaponSoulStripper::SpawnCanisterBeam( const char *bottom, const char *top, const idStr &beamName ) { + hhBeamSystem *system = NULL; + idVec3 boneOrigin; + idMat3 boneAxis; + + if( beamName.IsEmpty() ) + return NULL; + + GetJointWorldTransform( dict->GetString(bottom), boneOrigin, boneAxis); + + system = hhBeamSystem::SpawnBeam( boneOrigin, beamName, boneAxis, true ); + if ( !system ) { + return NULL; + } + + if (owner.IsValid()) { + system->snapshotOwner = owner.GetEntity(); //rww + } + + GetJointWorldTransform( dict->GetString(top), boneOrigin, boneAxis); + + system->SetTargetLocation( boneOrigin ); + system->GetRenderEntity()->weaponDepthHack = true; + system->GetRenderEntity()->allowSurfaceInViewID = owner->entityNumber+1; + system->fl.neverDormant = true; + + return system; +} + +//============================================================================= +// +// hhWeaponSoulStripper::SpawnCanisterSprite +// +//============================================================================= + +idEntity *hhWeaponSoulStripper::SpawnCanisterSprite( const char *attach, const char *spriteName ) { + idEntity *ent; + const char *jointName; + + assert(gameLocal.isMultiplayer); //rww - i don't see this being used in mp, but if it is, it needs to be reworked. + + jointName = dict->GetString( attach ); + ent = gameLocal.SpawnObject( dict->GetString( spriteName ) ); + if ( ent ) { + ent->MoveToJoint( this, jointName ); + ent->BindToJoint( this, jointName, false ); + ent->GetRenderEntity()->weaponDepthHack = true; + } + + return ent; +} + +hhEntityFx* hhWeaponSoulStripper::SpawnCanisterFx( const char *attach, const idStr &name ) { + const char *jointName; + idVec3 boneOrigin; + idMat3 boneAxis; + hhEntityFx* fx; + + if( name.IsEmpty() ) + return NULL; + + jointName = dict->GetString( attach ); + + GetJointWorldTransform( dict->GetString(attach), boneOrigin, boneAxis); + + hhFxInfo fxInfo; + fxInfo.SetEntity( this ); + fxInfo.SetBindBone( jointName ); + fx = SpawnFxLocal( name, boneOrigin, boneAxis, &fxInfo, true ); + fx->GetRenderEntity()->weaponDepthHack = true; + fx->GetRenderEntity()->allowSurfaceInViewID = owner->entityNumber+1; + //don't attach canister fx to snapshotOwner, since they are local and temporary + /* + if (owner.IsValid()) { + fx->snapshotOwner = owner.GetEntity(); //rww + } + */ + + return fx; +} + +//============================================================================= +// +// hhWeaponSoulStripper::CreateAltFireController +// +//============================================================================= +hhWeaponFireController* hhWeaponSoulStripper::CreateAltFireController() { + return hhWeapon::CreateAltFireController(); + //return new hhSoulStripperAltFireController; +} + +//============================================================================= +// +// hhWeaponSoulStripper::~hhWeaponSoulStripper +// +//============================================================================= + +hhWeaponSoulStripper::~hhWeaponSoulStripper() { + SAFE_REMOVE( beam ); + + DestroyCans(); + + // Remove the beam light + if ( beamLightHandle != -1 ) { + gameRenderWorld->FreeLightDef( beamLightHandle ); + beamLightHandle = -1; + } + + StopSound( SND_CHANNEL_WEAPON, false ); //rww - don't broadcast this. you're removing the entity on the client too, so not only would it get + //called there too, but once you receive the event the entity will be gone and it will be tossed. +} + +//============================================================================= +// +// hhWeaponSoulStripper::UpdateGUI +// +//============================================================================= +void hhWeaponSoulStripper::UpdateGUI() { + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->SetStateInt( "souls", (GetAnimPostfix()>0) ? GetAnimPostfix()-'A'+1 : 0 ); + float p = 3.0f * owner->inventory.AmmoPercentage(owner.GetEntity(), GetAmmoType()); + renderEntity.gui[0]->SetStateFloat( "soulpercentageA", idMath::ClampFloat(0.0f, 1.0f, p - 0.0f) ); + renderEntity.gui[0]->SetStateFloat( "soulpercentageB", idMath::ClampFloat(0.0f, 1.0f, p - 1.0f) ); + renderEntity.gui[0]->SetStateFloat( "soulpercentageC", idMath::ClampFloat(0.0f, 1.0f, p - 2.0f) ); + renderEntity.gui[0]->SetStateBool( "leeching", (targetNode) ? true : false); + + const char* str; + if( targetNode ) { + const idDict *energyDef; + str = targetNode->spawnArgs.GetString( "def_energy" ); + if ( str && str[0] ) { + energyDef = gameLocal.FindEntityDefDict( str ); + if ( energyDef ) { + renderEntity.gui[0]->SetStateFloat( "meter", (gameLocal.time - targetTime)/energyDef->GetFloat( "leech_time", "500" ) ); + renderEntity.gui[0]->SetStateInt( "leechtype", energyDef->GetInt("beamType")+1 ); + } + } + } + + if( fireController ) { + str = fireController->GetString("intgui"); + if( str && str[0] ) + renderEntity.gui[0]->SetStateInt( "type", str[0]-'1'+1 ); + } + else { + renderEntity.gui[0]->SetStateInt( "type", 0 ); + } + } +} + +void hhWeaponSoulStripper::Event_Leech() { + idVec3 start, end; + trace_t results; + int captured = 0; + + if ( !beam.IsValid() ) { + idThread::ReturnInt( 0 ); + return; + } + + start = GetOrigin(); + + // Currently latched onto a target, check to see if the target is within the valid cone and there is still a LOS to the target + if ( targetNode ) { + end = beam->GetTargetLocation(); + + // Verify that the target is within a cone in front of the weapon + idVec3 vecToTarget = end - start; + vecToTarget.Normalize(); + float dpX = GetAxis()[0] * vecToTarget; + float dpY = GetAxis()[1] * vecToTarget; + + // Target isn't in the cone, so reset the trace + if ( idMath::Fabs( dpY ) >= spawnArgs.GetFloat( "beamTargetAngle", "0.4" ) || !targetNode->CanLeech() ) { + //ReleaseEntity(); + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetNode = NULL; + end = start + maxBeamLength * GetAxis()[0]; + } + } + else + end = start + maxBeamLength * GetAxis()[0]; + + // Check to see if the beam should attempt to hit something + if (owner.IsValid()) { + //rww - can hit owners due to different angles in mp (although not the local client since the trace is done on the client every frame) + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, owner.GetEntity() ); + } + else { + gameLocal.clip.TracePoint( results, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_GAME_PORTAL, this ); + } + + beamLength = (results.endpos - start).Length(); + + // Hit something + if ( results.fraction < 1.0f ) { + captured = CaptureEnergy( results ); + + // Update the main beam + UpdateBeam( start, true ); // struck an entity + } + + //if( owner.IsValid() ) { + // owner->WeaponFireFeedback( dict ); + //} + + // Return true if the beam has struck an entity + idThread::ReturnFloat( (float)captured ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_RecoilBeam +// +//============================================================================= + +void hhWeaponSoulStripper::Event_EndLeech() { + idVec3 start; + idMat3 axis; + + if ( !beam.IsValid() ) { + return; + } + + if ( targetNode ) + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetNode = NULL; + + // Update the main beam + UpdateBeam( start, false ); + + if( owner.IsValid() ) { + owner->WeaponFireFeedback( dict ); + } + + idThread::ReturnFloat( 0 ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Ticker +// +// Controls the length of the beam and firing projectiles +//============================================================================= + +void hhWeaponSoulStripper::Ticker() { + // Update canister beams + UpdateCanisterBeam( beamCanA1.GetEntity(), "attach_topA", "attach_soulA" ); + UpdateCanisterBeam( beamCanB1.GetEntity(), "attach_topB", "attach_soulB" ); + UpdateCanisterBeam( beamCanC1.GetEntity(), "attach_topC", "attach_soulC" ); + UpdateCanisterBeam( beamCanA2.GetEntity(), "attach_bottomA", "attach_soulA" ); + UpdateCanisterBeam( beamCanB2.GetEntity(), "attach_bottomB", "attach_soulB" ); + UpdateCanisterBeam( beamCanC2.GetEntity(), "attach_bottomC", "attach_soulC" ); + UpdateCanisterBeam( beamCanA3.GetEntity(), "attach_bottomA", "attach_topA" ); + UpdateCanisterBeam( beamCanB3.GetEntity(), "attach_bottomB", "attach_topB" ); + UpdateCanisterBeam( beamCanC3.GetEntity(), "attach_bottomC", "attach_topC" ); + + hhBeamBasedFireController *beamController; + hhSunbeamFireController *sun; + if ( fireController && fireController->IsType(hhBeamBasedFireController::Type) ) { + if ( fireController->IsType(hhSunbeamFireController::Type) ) { + sun = static_cast(fireController); + sun->Think(); + } + else { + beamController = static_cast(fireController); + beamController->Think(); + } + } + + // Update our current animation if cans changed + if ( GetAnimPostfix() != lastCanState ) { + if ( GetAnimator()->IsAnimating( gameLocal.time ) && lastAnim.Length() != 0 ) { + idStr animname = lastAnim; + animname.Append( GetAnimPostfix() ); + int anim = GetAnimator()->GetAnim( animname ); + if( anim ) + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS(4) ); + } + + lastCanState = GetAnimPostfix(); + } +} + +//============================================================================= +// +// hhWeaponSoulStripper::UpdateBeam +// +// Updates the beam system +// - beam length +// - scale issues (don't let the beam wiggle as wildly when it is pulled close) +// - beam light +//============================================================================= + +void hhWeaponSoulStripper::UpdateBeam( idVec3 start, bool struckEntity ) { + float scale; + idVec3 boneOrigin; + idMat3 boneAxis; + + if ( !beam.IsValid() ) { + return; + } + + if ( targetNode ) { + beam->Activate( true ); + + GetJointWorldTransform( dict->GetString("attach_beam"), boneOrigin, boneAxis); + + beam->SetOrigin( boneOrigin ); + + beam->SetArcVector( GetAxis()[0] ); + scale = beamLength / 150.0f; + if( scale > 1 ) { + scale = 1; + } + + if ( targetNode->entityNumber < ENTITYNUM_MAX_NORMAL ) { + beam->SetTargetLocation( targetNode->leechPoint ); + } + + int beamType=0; + const idDict *energyDef; + const char* str = targetNode->spawnArgs.GetString( "def_energy" ); + if ( str && str[0] ) { + energyDef = gameLocal.FindEntityDefDict( str ); + if ( energyDef ) + beamType=energyDef->GetInt("beamType"); + } + + beam->SetShaderParm( SHADERPARM_MODE, beamType ); + beamLight.shaderParms[ SHADERPARM_MODE ] = beamType; + + beam->SetBeamOffsetScale( scale ); + + // Update the beam light + beamLight.axis[0] = GetAxis()[2]; + beamLight.axis[1] = GetAxis()[1]; + beamLight.axis[2] = GetAxis()[0]; // Re-align the light to point along the beam + beamLight.origin = (GetOrigin() + GetAxis()[0] * beamLength * 0.25f); // Set beam origin to the midpoint of the weapon origin and the target + beamLight.lightRadius[2] = 128.0f + beamLength * 0.5f; // Scale the light size based upon the beam (larger than half, so the beam extends beyond the extents slightly) + + if ( beamLightHandle != -1 ) { + gameRenderWorld->UpdateLightDef( beamLightHandle, &beamLight ); + } else { + beamLightHandle = gameRenderWorld->AddLightDef( &beamLight ); + } + } else { + beam->Activate( false ); + + // Hide the beam light + if ( beamLightHandle != -1 ) { + gameRenderWorld->FreeLightDef( beamLightHandle ); + beamLightHandle = -1; + } + } +} + +//============================================================================= +// +// hhWeaponSoulStripper::UpdateCanisterBeam +// +//============================================================================= + +void hhWeaponSoulStripper::UpdateCanisterBeam( hhBeamSystem *system, const char *top, const char *bottom ) { + idVec3 boneOrigin; + idMat3 boneAxis; + + if ( !system ) { + return; + } + + GetJointWorldTransform( dict->GetString( top ), boneOrigin, boneAxis); + system->SetTargetLocation( boneOrigin ); + GetJointWorldTransform( dict->GetString( bottom ), boneOrigin, boneAxis); + system->SetOrigin( boneOrigin ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::CaptureEnergy +// +//============================================================================= + +int hhWeaponSoulStripper::CaptureEnergy( trace_t &results ) { + idEntity *hitEntity; + hhEnergyNode* hitNode; + + hitEntity = gameLocal.GetTraceEntity(results); + + if ( hitEntity && hitEntity->IsType( hhEnergyNode::Type ) ) { + hitNode = static_cast(hitEntity); + if ( !hitNode->CanLeech() ) + return 0; + } + else { + if ( targetNode ) + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetNode = NULL; + return 0; + } + + if ( targetNode != hitNode ) { + if ( targetNode ) + targetNode->LeechTrigger(GetOwner(),"leech_end"); + targetTime = gameLocal.time; + targetNode = hitNode; + targetNode->LeechTrigger(GetOwner(),"leech_start"); + } + + const idDict *energyDef; + const char* str = targetNode->spawnArgs.GetString( "def_energy" ); + if ( !str || !str[0] ) + return 0; // blank node + + energyDef = gameLocal.FindEntityDefDict( str ); + if ( !energyDef ) + return 0; + + if ( gameLocal.time - targetTime < energyDef->GetInt( "leech_time", "500" ) ) + return 1; //not enough time + + targetNode->LeechTrigger(GetOwner(),"leech_success"); + targetNode->Finish(); + targetTime = gameLocal.time; + + //create new firecontroller for new energy type + if( !GiveEnergy( energyDef->GetString("def_fireInfo"), true ) ) + return 0; + + StartSound( "snd_intake", SND_CHANNEL_ANY ); + SpawnCanisterFx( "attach_sidevent", spawnArgs.GetString("fx_sidevent") ); + SpawnCanisterFx( "attach_steam", spawnArgs.GetString("fx_steam") ); + + owner->inventory.energyType = str; + + return 2; +} + +bool hhWeaponSoulStripper::GiveEnergy( const char *energyType, bool fill ) { + if ( !energyType || !energyType[0] ) + return false; + + //create new firecontroller for new energy type + if (gameLocal.isClient) //rww - handle this properly through snapshots + return true; + + SAFE_DELETE_PTR(fireController); + const idDeclEntityDef *fcDecl = gameLocal.FindEntityDef( energyType, false ); + const idDict* infoDict = fcDecl ? &fcDecl->dict : NULL; + fcDeclNum = 0; + if( infoDict ) { + const char *spawn; + idTypeInfo *cls; + + if( fill ) { + int num = owner->GetWeaponNum("weaponobj_soulstripper"); + assert(num); + owner->weaponInfo[ num ].ammoMax = infoDict->GetInt("ammoAmount"); + owner->spawnArgs.SetInt( "max_ammo_energy", infoDict->GetInt("ammoAmount") ); + owner->inventory.ammo[owner->inventory.AmmoIndexForAmmoClass("ammo_energy")]=0; + owner->Give( "ammo_energy", infoDict->GetString("ammoAmount") ); + } + beam_canTop = infoDict->GetString( "beam_canTop" ); + beam_canBot = infoDict->GetString( "beam_canBot" ); + beam_canGlow = infoDict->GetString( "beam_canGlow" ); + fx_can = infoDict->GetString( "fx_can" ); + + DestroyCans(); + SpawnCans(); + + infoDict->GetString( "spawnclass", NULL, &spawn ); + if ( !spawn || !spawn[0] ) { //rww - changed to be in line with client behavior + spawn = "hhWeaponFireController"; //default to regular fire controller + } + cls = idClass::GetClass( spawn ); + if ( !cls || !cls->IsType( hhWeaponFireController::Type ) ) { + common->Warning( "Could not spawn. Class '%s' not found.", spawn ); + return false; + } + fireController = static_cast( cls->CreateInstance() ); + if ( !fireController ) { + common->Warning( "Could not spawn '%s'. Instance could not be created.", spawn ); + return false; + } + fcDeclNum = fcDecl->Index(); + /* + else { + fireController = CreateFireController(); + } + */ + + fireController->Init( infoDict, this, owner.GetEntity() ); + } + + //if it failed, use generic empty controller + if ( !fireController ) { + const idDict* infoDict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_fireInfo"), false ); + if( infoDict ) { + fireController = CreateFireController(); + fireController->Init( infoDict, this, owner.GetEntity() ); + } + } + return true; +} + +//============================================================================= +// +// hhWeaponSoulStripper::GetAnimPostfix +// +// Appends to the passed in postfix. +//============================================================================= + +char hhWeaponSoulStripper::GetAnimPostfix() { + // Choose the anim postfix based upon ammo + float p=owner->inventory.AmmoPercentage(owner.GetEntity(), GetAmmoType()); + if(p > 0) + return (int)(2.9f*p) + 'A'; + else + return 0; +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_GetAnimPostFix +// +//============================================================================= + +void hhWeaponSoulStripper::Event_GetAnimPostFix() { + idStr postfix( GetAnimPostfix() ); + idThread::ReturnString( postfix.c_str() ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_PlayAnim +// +//============================================================================= + +void hhWeaponSoulStripper::Event_PlayAnim( int channel, const char *animname ) { + lastAnim = ""; + + idStr anim = animname; + anim.Append( GetAnimPostfix() ); + hhWeapon::Event_PlayAnim( channel, anim.c_str() ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Event_PlayCycle +// +//============================================================================= + +void hhWeaponSoulStripper::Event_PlayCycle( int channel, const char *animname ) { + lastAnim = animname; + + idStr anim = animname; + anim.Append( GetAnimPostfix() ); + hhWeapon::Event_PlayCycle( channel, anim.c_str() ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::PresentWeapon +// +//============================================================================= + +void hhWeaponSoulStripper::PresentWeapon( bool showViewModel ) { + if ( IsHidden() || !owner->CanShowWeaponViewmodel() || pm_thirdPerson.GetBool() ) { + if ( beamCanA1.IsValid() ) beamCanA1->Activate( false ); + if ( beamCanB1.IsValid() ) beamCanB1->Activate( false ); + if ( beamCanC1.IsValid() ) beamCanC1->Activate( false ); + if ( beamCanA2.IsValid() ) beamCanA2->Activate( false ); + if ( beamCanB2.IsValid() ) beamCanB2->Activate( false ); + if ( beamCanC2.IsValid() ) beamCanC2->Activate( false ); + if ( beamCanA3.IsValid() ) beamCanA3->Activate( false ); + if ( beamCanB3.IsValid() ) beamCanB3->Activate( false ); + if ( beamCanC3.IsValid() ) beamCanC3->Activate( false ); + if ( fxCanA.IsValid() ) fxCanA->Hide(); + if ( fxCanB.IsValid() ) fxCanB->Hide(); + if ( fxCanC.IsValid() ) fxCanC->Hide(); + } else { + if ( beamCanA1.IsValid() ) beamCanA1->Activate( true ); + if ( beamCanB1.IsValid() ) beamCanB1->Activate( true ); + if ( beamCanC1.IsValid() ) beamCanC1->Activate( true ); + if ( beamCanA2.IsValid() ) beamCanA2->Activate( true ); + if ( beamCanB2.IsValid() ) beamCanB2->Activate( true ); + if ( beamCanC2.IsValid() ) beamCanC2->Activate( true ); + if ( beamCanA3.IsValid() ) beamCanA3->Activate( true ); + if ( beamCanB3.IsValid() ) beamCanB3->Activate( true ); + if ( beamCanC3.IsValid() ) beamCanC3->Activate( true ); + if ( fxCanA.IsValid() ) fxCanA->Show(); + if ( fxCanB.IsValid() ) fxCanB->Show(); + if ( fxCanC.IsValid() ) fxCanC->Show(); + } + + hhWeapon::PresentWeapon( showViewModel ); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Show +// +//============================================================================= + +void hhWeaponSoulStripper::Show() { + if ( beamCanA1.IsValid() ) beamCanA1->Show(); + if ( beamCanB1.IsValid() ) beamCanB1->Show(); + if ( beamCanC1.IsValid() ) beamCanC1->Show(); + if ( beamCanA2.IsValid() ) beamCanA2->Show(); + if ( beamCanB2.IsValid() ) beamCanB2->Show(); + if ( beamCanC2.IsValid() ) beamCanC2->Show(); + if ( beamCanA3.IsValid() ) beamCanA3->Show(); + if ( beamCanB3.IsValid() ) beamCanB3->Show(); + if ( beamCanC3.IsValid() ) beamCanC3->Show(); + if ( fxCanA.IsValid() ) fxCanA->Show(); + if ( fxCanB.IsValid() ) fxCanB->Show(); + if ( fxCanC.IsValid() ) fxCanC->Show(); + + hhWeapon::Show(); +} + +//============================================================================= +// +// hhWeaponSoulStripper::Hide +// +//============================================================================= + +void hhWeaponSoulStripper::Hide() { + if ( beamCanA1.IsValid() ) beamCanA1->Hide(); + if ( beamCanB1.IsValid() ) beamCanB1->Hide(); + if ( beamCanC1.IsValid() ) beamCanC1->Hide(); + if ( beamCanA2.IsValid() ) beamCanA2->Hide(); + if ( beamCanB2.IsValid() ) beamCanB2->Hide(); + if ( beamCanC2.IsValid() ) beamCanC2->Hide(); + if ( beamCanA3.IsValid() ) beamCanA3->Hide(); + if ( beamCanB3.IsValid() ) beamCanB3->Hide(); + if ( beamCanC3.IsValid() ) beamCanC3->Hide(); + if ( fxCanA.IsValid() ) fxCanA->Hide(); + if ( fxCanB.IsValid() ) fxCanB->Hide(); + if ( fxCanC.IsValid() ) fxCanC->Hide(); + + hhWeapon::Hide(); +} + + +/* +================ +hhWeaponSoulStripper::Save +================ +*/ +void hhWeaponSoulStripper::Save( idSaveGame *savefile ) const { + savefile->WriteRenderLight( beamLight ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->WriteInt( beamLightHandle ); + + beam.Save( savefile ); + beamCanA1.Save( savefile ); + beamCanB1.Save( savefile ); + beamCanC1.Save( savefile ); + beamCanA2.Save( savefile ); + beamCanB2.Save( savefile ); + beamCanC2.Save( savefile ); + beamCanA3.Save( savefile ); + beamCanB3.Save( savefile ); + beamCanC3.Save( savefile ); + fxCanA.Save( savefile ); + fxCanB.Save( savefile ); + fxCanC.Save( savefile ); + savefile->WriteBool(cansValid); + + savefile->WriteFloat( beamLength ); + savefile->WriteFloat( maxBeamLength ); + savefile->WriteObject( targetNode ); + savefile->WriteVec3( targetOffset ); + + savefile->WriteString( lastAnim ); +// savefile->WriteInt( lastAltAmmo ); +} + +/* +================ +hhWeaponSoulStripper::Restore +================ +*/ +void hhWeaponSoulStripper::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderLight( beamLight ); + //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles + //savefile->ReadInt( beamLightHandle ); + + beam.Restore( savefile ); + beamCanA1.Restore( savefile ); + beamCanB1.Restore( savefile ); + beamCanC1.Restore( savefile ); + beamCanA2.Restore( savefile ); + beamCanB2.Restore( savefile ); + beamCanC2.Restore( savefile ); + beamCanA3.Restore( savefile ); + beamCanB3.Restore( savefile ); + beamCanC3.Restore( savefile ); + fxCanA.Restore( savefile ); + fxCanB.Restore( savefile ); + fxCanC.Restore( savefile ); + savefile->ReadBool(cansValid); + + savefile->ReadFloat( beamLength ); + savefile->ReadFloat( maxBeamLength ); + savefile->ReadObject( reinterpret_cast ( targetNode ) ); + savefile->ReadVec3( targetOffset ); + + savefile->ReadString( lastAnim );; +} + +/* +================= +hhWeaponSoulStripper::ReadFromSnapshot +rww - update the fire controller on the client +================= +*/ +void hhWeaponSoulStripper::ClientUpdateFC(int fcType, int fcDefNumber) { + SAFE_DELETE_PTR(fireController); + + idTypeInfo *typeInfo = idClass::GetType(fcType); + const idDeclEntityDef *fcDecl = static_cast(declManager->DeclByIndex(DECL_ENTITYDEF, fcDefNumber, false)); + if (typeInfo && fcDecl) { + int num = owner->GetWeaponNum("weaponobj_soulstripper"); + assert(num); + owner->weaponInfo[ num ].ammoMax = fcDecl->dict.GetInt("ammoAmount"); + owner->spawnArgs.SetInt( "max_ammo_energy", fcDecl->dict.GetInt("ammoAmount") ); + beam_canTop = fcDecl->dict.GetString( "beam_canTop" ); + beam_canBot = fcDecl->dict.GetString( "beam_canBot" ); + beam_canGlow = fcDecl->dict.GetString( "beam_canGlow" ); + fx_can = fcDecl->dict.GetString( "fx_can" ); + DestroyCans(); + SpawnCans(); + + fireController = static_cast(typeInfo->CreateInstance()); + if (fireController) { + fireController->Init(&fcDecl->dict, this, owner.GetEntity()); + } + } +} + +/* +================= +hhWeaponSoulStripper::WriteToSnapshot +rww - write applicable weapon values to snapshot +================= +*/ +void hhWeaponSoulStripper::WriteToSnapshot( idBitMsgDelta &msg ) const { + //target entity + if (targetNode) { + msg.WriteBits(1, 1); + msg.WriteBits(targetNode->entityNumber, GENTITYNUM_BITS); + } + else { + msg.WriteBits(0, 1); + } + + //write the fire controller type + if (fireController) { + msg.WriteBits(fireController->GetType()->typeNum, idClass::GetTypeNumBits()); + msg.WriteBits(gameLocal.ServerRemapDecl(-1, DECL_ENTITYDEF, fcDeclNum ), gameLocal.entityDefBits); + } + else { + msg.WriteBits(0, idClass::GetTypeNumBits()); + msg.WriteBits(0, gameLocal.entityDefBits); + } + + hhWeapon::WriteToSnapshot(msg); +} + +/* +================= +hhWeaponSoulStripper::ReadFromSnapshot +rww - read applicable weapon values from snapshot +================= +*/ +void hhWeaponSoulStripper::ReadFromSnapshot( const idBitMsgDelta &msg ) { + //target entity + bool hasEnt = !!msg.ReadBits(1); + if (hasEnt) { + int entNum = msg.ReadBits(GENTITYNUM_BITS); + targetNode = static_cast(gameLocal.entities[entNum]); + } + else { + targetNode = NULL; + } + + int fcType = msg.ReadBits(idClass::GetTypeNumBits()); + int fcDefNumber = gameLocal.ClientRemapDecl(DECL_ENTITYDEF, msg.ReadBits(gameLocal.entityDefBits)); + + //standard weapon stuff (primarily done before switch logic to get owner) + hhWeapon::ReadFromSnapshot(msg); + + if (owner.IsValid() && fcType && (!fireController || fcType != fireController->GetType()->typeNum || !netInitialized)) { + //if there is no fire controller on the client or the fire controller does not much the type given by the server, switch + ClientUpdateFC(fcType, fcDefNumber); + netInitialized = true; + } +} + +/* +================ +hhWeaponSoulStripper::GetClipBits +================ +*/ +int hhWeaponSoulStripper::GetClipBits(void) const { + return 12; //0-4096 +} + +void hhWeaponSoulStripper::Event_LightFadeIn( float fadetime ) { + if (gameLocal.isMultiplayer) { //rww - none of this in mp + return; + } + idEntity *ent; + idEntity *next; + idLight *light; + idVec3 color; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); + if( !gameLocal.InPlayerPVS(ent) ) + continue; + if ( !ent->IsType( idLight::Type ) ) + continue; + light = static_cast( ent ); + if ( light->GetMaterial()->IsFogLight() ) + continue; + light->GetColor( color ); + if ( color == idVec3(0.001f,0.f,0.f) ) + light->FadeIn( fadetime ); + } +} + +void hhWeaponSoulStripper::Event_LightFadeOut( float fadetime ) { + if (gameLocal.isMultiplayer) { //rww - none of this in mp + return; + } + idEntity *ent; + idEntity *next; + idLight *light; + idVec3 color; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); + if( !gameLocal.InPlayerPVS(ent) ) + continue; + if ( !ent->IsType( idLight::Type ) ) + continue; + light = static_cast( ent ); + if ( light->GetMaterial()->IsFogLight() ) + continue; + light->GetColor( color ); + if( light->spawnArgs.GetVector("_color", "1 1 1") == color && light->GetCurrentLevel()==1 ) + light->Fade(idVec4(0.001f,0.f,0.f,0.f), fadetime ); + } +} + +void hhWeaponSoulStripper::Event_LightFade( float fadeOut, float pause, float fadeIn ) { + PostEventSec( &EV_LightFadeOut, 0, fadeOut ); + PostEventSec( &EV_LightFadeIn, pause, fadeIn ); +} + +void hhWeaponSoulStripper::Event_GetFireFunction() { + if ( fireController ) { + idThread::ReturnString( fireController->GetScriptFunction() ); + } +} + +void hhWeaponSoulStripper::Event_KillBeam() { + if ( fireController && fireController->IsType(hhBeamBasedFireController::Type) ) { + hhBeamBasedFireController *beamController = static_cast(fireController); + beamController->KillBeam(); + } +} + +/*void hhWeaponSoulStripper::Event_GetFireSound() { + if ( fireController ) { + idThread::ReturnString( fireController->GetScriptFunction() ); + } +}*/ \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponsoulstripper.h b/neo/prey/Prey/prey_weaponsoulstripper.h new file mode 100644 index 00000000..6e983568 --- /dev/null +++ b/neo/prey/Prey/prey_weaponsoulstripper.h @@ -0,0 +1,182 @@ +#ifndef __HH_WEAPON_SOULSTRIPPER_H +#define __HH_WEAPON_SOULSTRIPPER_H + + +/*********************************************************************** + + hhSoulStripperAltFireController + +***********************************************************************/ +class hhSoulStripperAltFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhSoulStripperAltFireController); +public: + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); +}; + + +/*********************************************************************** + + hhBeamBasedFireController + +***********************************************************************/ +class hhBeamBasedFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhBeamBasedFireController); +public: + virtual ~hhBeamBasedFireController(); + virtual void Init( const idDict* viewDict, hhWeapon* self, hhPlayer* owner ); + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); + + void KillBeam(); + void Think(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idEntityPtr shotbeam; + int projTime; + idEntityPtr impactFx; +}; + +/*********************************************************************** + + hhSunbeamFireController + +***********************************************************************/ +class hhSunbeamFireController : public hhBeamBasedFireController { + CLASS_PROTOTYPE(hhSunbeamFireController); +public: + virtual void LaunchProjectiles( const idVec3& launchOrigin, const idMat3& aimAxis, const idVec3& pushVelocity, idEntity* projOwner ); + void Think(); +}; + +/*********************************************************************** + + hhPlasmaFireController + +***********************************************************************/ +class hhPlasmaFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhPlasmaFireController); +public: + virtual bool LaunchProjectiles( const idVec3& pushVelocity ); +}; + +/*********************************************************************** + + hhWeaponSoulStripper + +***********************************************************************/ +#define MAX_SOUL_AMMO 3 + +typedef enum +{ + CAP_NONE = 0, + CAP_ENTITY, + CAP_INTAKE +} captureType_t; + +class hhWeaponSoulStripper : public hhWeapon { + CLASS_PROTOTYPE( hhWeaponSoulStripper ); + + public: + hhWeaponSoulStripper(); + void Spawn(); + virtual ~hhWeaponSoulStripper(); + + virtual void ParseDef( const char* objectname ); + + virtual void UpdateGUI(); + + virtual void Show(); + virtual void Hide(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void ClientUpdateFC(int fcType, int fcDefNumber); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual int GetClipBits(void) const; + + // beam light + renderLight_t beamLight; + int beamLightHandle; + + bool GiveEnergy( const char *energyType, bool fill ); + + void CheckCans(void); //rww + protected: + virtual void Ticker(); + char GetAnimPostfix(); + virtual void PresentWeapon( bool showViewModel ); + virtual hhWeaponFireController* CreateAltFireController(); + + void UpdateBeam( idVec3 start, bool struckEntity ); + void UpdateCanisterBeam( hhBeamSystem *system, const char *top, const char *bottom ); + + int CaptureEnergy( trace_t &results ); + + void Event_PostSpawn(); + void Event_GetAnimPostFix(); + void Event_Leech(); + void Event_EndLeech(); + + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + + void SpawnCans(); + void DestroyCans(); + + hhBeamSystem *SpawnCanisterBeam( const char *bottom, const char *top, const idStr &beamName ); + idEntity *SpawnCanisterSprite( const char *attach, const char *spriteName ); + hhEntityFx *SpawnCanisterFx( const char *attach, const idStr &name ); + + void Event_LightFadeIn( float fadetime ); + void Event_LightFadeOut( float fadetime ); + void Event_LightFade( float fadeOut, float pause, float fadeIn ); + void Event_GetFireFunction(); + void Event_KillBeam(); + + protected: + idEntityPtr beam; + + // Beams for each canister + idEntityPtr beamCanA1; // bottom to center + idEntityPtr beamCanB1; // bottom to center + idEntityPtr beamCanC1; // bottom to center + idEntityPtr beamCanA2; // top to center + idEntityPtr beamCanB2; // top to center + idEntityPtr beamCanC2; // top to center + idEntityPtr beamCanA3; // top to bottom + idEntityPtr beamCanB3; // top to bottom + idEntityPtr beamCanC3; // top to bottom + + idEntityPtr fxCanA; + idEntityPtr fxCanB; + idEntityPtr fxCanC; + + float beamLength; + float maxBeamLength; + + hhEnergyNode *targetNode; // Target struck by the beam + idVec3 targetOffset; // Offset from the origin of target + int targetTime; + + idStr lastAnim; // For smoothing the animation when picking up an ammo_soul -mdl + char lastCanState; + + bool empty; + + int fcDeclNum; //rww - needed for networking (and a good idea to have around anyway) + + idStr beam_canTop; + idStr beam_canBot; + idStr beam_canGlow; + idStr fx_can; + bool netInitialized; //rww + bool cansValid; //rww +}; + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/prey_weaponspiritbow.cpp b/neo/prey/Prey/prey_weaponspiritbow.cpp new file mode 100644 index 00000000..38e1f5cc --- /dev/null +++ b/neo/prey/Prey/prey_weaponspiritbow.cpp @@ -0,0 +1,378 @@ +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/*********************************************************************** + + hhSpiritBowFireController + +***********************************************************************/ +CLASS_DECLARATION( hhWeaponFireController, hhSpiritBowFireController ) +END_CLASS + +/* +================= +hhSpiritBowFireController::AmmoRequired +================= +*/ +int hhSpiritBowFireController::AmmoRequired() const { + if( owner->IsDeathWalking() || owner->IsPossessed() ) { + return 0; + } + else { // CJR: will use any available ammo until the player is at zero ammo + int ammoAmount = owner->inventory.ammo[ GetAmmoType() ]; + if ( ammoAmount > 0 && ammoAmount < ammoRequired ) { + return ammoAmount; + } + } + + return ammoRequired; +} + +/* +================= +hhSpiritBowFireController::GetProjectileDict +================= +*/ +const idDict* hhSpiritBowFireController::GetProjectileDict() const +{ + //if( owner->inventory.maxHealth > 100 && !owner->IsDeathWalking() && !gameLocal.isMultiplayer ) + // return gameLocal.FindEntityDefDict( dict->GetString("def_projectileSuper"), false ); + + return hhWeaponFireController::GetProjectileDict(); +} + +/*********************************************************************** + + hhWeaponSpiritBow + +***********************************************************************/ +const idEventDef EV_UpdateBowVision( "updateBowVision" ); +const idEventDef EV_StartSeeThroughWalls( "startBowVision" ); +const idEventDef EV_FadeOutSeeThroughWalls( "fadeOutBowVision" ); +const idEventDef EV_StopSeeThroughWalls( "stopBowVision" ); +const idEventDef EV_BowVisionIsEnabled( "visionIsEnabled", NULL, 'd' ); + +CLASS_DECLARATION( hhWeaponZoomable, hhWeaponSpiritBow ) + EVENT( EV_UpdateBowVision, hhWeaponSpiritBow::Event_UpdateBowVision ) + EVENT( EV_StartSeeThroughWalls, hhWeaponSpiritBow::Event_StartSeeThroughWalls ) + EVENT( EV_FadeOutSeeThroughWalls, hhWeaponSpiritBow::Event_FadeOutSeeThroughWalls ) + EVENT( EV_StopSeeThroughWalls, hhWeaponSpiritBow::Event_StopSeeThroughWalls ) + EVENT( EV_BowVisionIsEnabled, hhWeaponSpiritBow::Event_BowVisionIsEnabled ) +END_CLASS + +/* +================= +hhWeaponSpiritBow::~hhWeaponSpiritBow +================= +*/ +hhWeaponSpiritBow::~hhWeaponSpiritBow() { + if( BowVisionIsEnabled() ) { + BowVisionIsEnabled( false ); + StopBowVision(); + } +} + +/* +================= +hhWeaponSpiritBow::Spawn +================= +*/ +void hhWeaponSpiritBow::Spawn() { + updateRover = 0; //rww - must be initialized! (sync'd over net) + BowVisionIsEnabled( false ); +} + +/* +================= +hhWeaponSpiritBow::BeginAltAttack +================= +*/ +void hhWeaponSpiritBow::BeginAltAttack( void ) { + if (owner->inventory.requirements.bCanUseBowVision) { + hhWeapon::BeginAltAttack(); + } +} + +/* +================= +hhWeaponSpiritBow::Ticker +================= +*/ +void hhWeaponSpiritBow::Ticker() { + // Fade out the alt-mode effect + if (!fadeAlpha.IsDone(gameLocal.GetTime())) { + float alpha = fadeAlpha.GetCurrentValue(gameLocal.GetTime()); + owner->playerView.SetViewOverlayColor(idVec4(alpha, alpha, alpha, alpha)); + } +} + +/* +================= +hhWeaponSpiritBow::GetProxyOf +================= +*/ +hhTargetProxy *hhWeaponSpiritBow::GetProxyOf( const idEntity *target ) { + hhTargetProxy *proxy; + // Determine whether this target is proxied by searching their bind list + for( idEntity* entity = target->GetTeamChain(); entity; entity = entity->GetTeamChain() ) { + if( entity && entity->IsType(hhTargetProxy::Type) ) { + proxy = static_cast(entity); + if( owner == proxy->GetOwner() ) { + return proxy; + } + } + } + + return NULL; +} + +/* +================= +hhWeaponSpiritBow::ProxyShouldBeVisible +================= +*/ +#define NOTVISIBLE 0 +#define ISVISIBLE 1 +#define FORCEVISIBILITY 2 +bool hhWeaponSpiritBow::ProxyShouldBeVisible( const idEntity* ent ) { + int visibilityType = ent->spawnArgs.GetInt("bowVisibilityType", "1"); + + if( visibilityType == FORCEVISIBILITY ) { + return true; + } + + return ent->fl.takedamage && visibilityType >= ISVISIBLE; +} + +/* +================= +hhWeaponSpiritBow::StopBowVision +================= +*/ +void hhWeaponSpiritBow::StopBowVision() { + if( !owner.IsValid() || !owner.GetEntity() || !owner->IsType(hhPlayer::Type) ) { + return; + } + + // Remove overlay + if( owner->IsSpiritWalking() ) { + owner->playerView.SetViewOverlayMaterial( declManager->FindMaterial(owner->spawnArgs.GetString("mtr_Spiritwalk")) ); + } + else { + owner->playerView.SetViewOverlayMaterial( NULL ); + } +} + +/* +================= +hhWeaponSpiritBow::Event_UpdateBowVision +================= +*/ +void hhWeaponSpiritBow::Event_UpdateBowVision() { + hhTargetProxy *proxy = NULL; + idEntity *ent = NULL; + + // In order to hit all possible entities (active or inactive) at low cost, we use a rover + // to traverse the entity list a little each tick, making a complete traversal about once + // a second. + + float fuse = altFireController->GetProjectileDict()->GetFloat( "fuse" ); + idVec3 velocity = altFireController->GetProjectileDict()->GetVector( "velocity" ); + float maxDistance = velocity.Length() * fuse; + float distSquared = maxDistance * maxDistance; + + // for all damageable active entities in the fuse radius + for (int ix=0; ix= MAX_GENTITIES) { + updateRover = 0; + } + ent = gameLocal.entities[updateRover]; + if (!ent || owner==ent ) { + continue; + } + + if( !ProxyShouldBeVisible(ent) ) { + continue; + } + + // PVS Check? + + if ((ent->GetOrigin() - GetOrigin()).LengthSqr() > distSquared) { + continue; + } + + // if already has proxy, continue + proxy = GetProxyOf(ent); + if (proxy) { + proxy->StayAlive(); + continue; + } + + if (!gameLocal.isClient) { + proxy = static_cast( gameLocal.SpawnObject(dict->GetString("def_targetproxy")) ); + if (proxy) { + proxy->SetOriginal(ent); + proxy->SetOwner(owner.GetEntity()); + proxy->UpdateVisualState(); + } + } + } +} + +/* +================= +hhWeaponSpiritBow::Event_StartSeeThroughWalls +================= +*/ +void hhWeaponSpiritBow::Event_StartSeeThroughWalls() { + // Cancel any pending removals/fades + CancelEvents( &EV_StopSeeThroughWalls ); + + updateRover = 0; // start at begining of list + + fadeAlpha.Init(gameLocal.GetTime(), BOWVISION_FADEIN_DURATION, 0.0f, 1.0f); + + StartSound( "snd_altmodefadein", SND_CHANNEL_ANY, 0, true, NULL ); + + // Apply overlay material + const idMaterial *mat = declManager->FindMaterial( spawnArgs.GetString("mtr_overlay") ); + owner->playerView.SetViewOverlayMaterial( mat ); + owner->playerView.SetViewOverlayColor( idVec4(1.0f, 1.0f, 1.0f, 0.0f) ); + + BowVisionIsEnabled( true ); +} + +/* +================= +hhWeaponSpiritBow::Event_FadeOutSeeThroughWalls + Fading the effect out, notify proxies to start fading too +================= +*/ +void hhWeaponSpiritBow::Event_FadeOutSeeThroughWalls() { + idEntity *ent; + hhTargetProxy *proxy; + + fadeAlpha.Init(gameLocal.GetTime(), BOWVISION_FADEOUT_DURATION, 1.0f, 0.0f); + owner->playerView.SetViewOverlayColor(colorWhite); + + StartSound( "snd_altmodefadeout", SND_CHANNEL_ANY, 0, true, NULL ); + +/*FIXME: Should use this format instead for speed +for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idLight::Type ) ) { + idLight *light = static_cast(ent); + } +}*/ + //TODO: Could store all created(owned) proxies in a list, for faster freeing here + // Unmark all proxies that were set for this owner + for (int ix=0; ixIsType(hhTargetProxy::Type)) { + continue; + } + + proxy = static_cast(ent); + if (owner==proxy->GetOwner()) { + proxy->ProxyFinished(); + } + } + + PostEventMS( &EV_StopSeeThroughWalls, BOWVISION_FADEOUT_DURATION ); + + BowVisionIsEnabled( false ); +} + +/* +================= +hhWeaponSpiritBow::Event_StopSeeThroughWalls +================= +*/ +void hhWeaponSpiritBow::Event_StopSeeThroughWalls() { + BowVisionIsEnabled( false ); + StopBowVision(); +} + +/* +================= +hhWeaponSpiritBow::Event_BowVisionIsEnabled +================= +*/ +void hhWeaponSpiritBow::Event_BowVisionIsEnabled() { + idThread::ReturnInt( BowVisionIsEnabled() ); +} + +/* +================ +hhWeaponSpiritBow::Save +================ +*/ +void hhWeaponSpiritBow::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( fadeAlpha.GetStartTime() ); // idInterpolate + savefile->WriteFloat( fadeAlpha.GetDuration() ); + savefile->WriteFloat( fadeAlpha.GetStartValue() ); + savefile->WriteFloat( fadeAlpha.GetEndValue() ); + + savefile->WriteInt( updateRover ); + savefile->WriteBool( visionEnabled ); +} + +/* +================ +hhWeaponSpiritBow::Restore +================ +*/ +void hhWeaponSpiritBow::Restore( idRestoreGame *savefile ) { + float set; + + savefile->ReadFloat( set ); // idInterpolate + fadeAlpha.SetStartTime( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetDuration( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetStartValue( set ); + savefile->ReadFloat( set ); + fadeAlpha.SetEndValue( set ); + + savefile->ReadInt( updateRover ); + savefile->ReadBool( visionEnabled ); +} + +/* +================= +hhWeaponSpiritBow::WriteToSnapshot +rww - write applicable weapon values to snapshot +================= +*/ +void hhWeaponSpiritBow::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits(visionEnabled, 1); + msg.WriteBits(updateRover, GENTITYNUM_BITS); + +// msg.WriteFloat(fadeAlpha.GetCurrentValue(gameLocal.time)); + msg.WriteFloat(fadeAlpha.GetDuration()); +// msg.WriteFloat(fadeAlpha.GetEndTime()); + msg.WriteFloat(fadeAlpha.GetEndValue()); + msg.WriteFloat(fadeAlpha.GetStartTime()); + msg.WriteFloat(fadeAlpha.GetStartValue()); + + hhWeapon::WriteToSnapshot(msg); +} + +/* +================= +hhWeaponSpiritBow::ReadFromSnapshot +rww - read applicable weapon values from snapshot +================= +*/ +void hhWeaponSpiritBow::ReadFromSnapshot( const idBitMsgDelta &msg ) { + visionEnabled = !!msg.ReadBits(1); + updateRover = msg.ReadBits(GENTITYNUM_BITS); + + fadeAlpha.SetDuration(msg.ReadFloat()); + fadeAlpha.SetEndValue(msg.ReadFloat()); + fadeAlpha.SetStartTime(msg.ReadFloat()); + fadeAlpha.SetStartValue(msg.ReadFloat()); + + hhWeapon::ReadFromSnapshot(msg); +} diff --git a/neo/prey/Prey/prey_weaponspiritbow.h b/neo/prey/Prey/prey_weaponspiritbow.h new file mode 100644 index 00000000..6c206821 --- /dev/null +++ b/neo/prey/Prey/prey_weaponspiritbow.h @@ -0,0 +1,63 @@ +#ifndef __HH_WEAPON_SPIRITBOW_H +#define __HH_WEAPON_SPIRITBOW_H + +/*********************************************************************** + + hhWeaponSpiritBow + +***********************************************************************/ + +class hhSpiritBowFireController : public hhWeaponFireController { + CLASS_PROTOTYPE(hhSpiritBowFireController); +public: + int AmmoRequired() const; + virtual const idDict* GetProjectileDict() const; +}; + +class hhWeaponSpiritBow : public hhWeaponZoomable { + CLASS_PROTOTYPE( hhWeaponSpiritBow ); + + public: + ~hhWeaponSpiritBow(); + + void Spawn(); + + bool BowVisionIsEnabled() const { return visionEnabled; } + void BowVisionIsEnabled( bool enabled ) { visionEnabled = enabled; } + void StopBowVision(); + virtual void BeginAltAttack( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + //rww - network friendliness + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + protected: + virtual void Ticker(); + hhTargetProxy * GetProxyOf( const idEntity *target ); + bool ProxyShouldBeVisible( const idEntity* ent ); + ID_INLINE virtual hhWeaponFireController* CreateFireController(); + + protected: + void Event_StartSeeThroughWalls(); + void Event_FadeOutSeeThroughWalls(); + void Event_StopSeeThroughWalls(); + void Event_UpdateBowVision(); + + void Event_BowVisionIsEnabled(); + + protected: + idInterpolate fadeAlpha; + int updateRover; + + bool visionEnabled; +}; + +ID_INLINE hhWeaponFireController* hhWeaponSpiritBow::CreateFireController() { + return new hhSpiritBowFireController; +} + + +#endif \ No newline at end of file diff --git a/neo/prey/Prey/sys_debugger.cpp b/neo/prey/Prey/sys_debugger.cpp new file mode 100644 index 00000000..e926c4c8 --- /dev/null +++ b/neo/prey/Prey/sys_debugger.cpp @@ -0,0 +1,1102 @@ +// sys_debugger.cpp +// +// HUMANHEAD: debugger functions +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + +/* TODO: + make dormant filter? + make physics filter? + Optimize: + preallocate all the string ptrs in DisplayCell and don't free them every frame +*/ + +#if INGAME_DEBUGGER_ENABLED + +hhDebugger debugger; + +int hhDebugger::sortColumn = 1; +bool hhDebugger::sortAscending = false; + +//========================================================================= +// +// hhDisplayCell +// +//========================================================================= + +hhDisplayCell::hhDisplayCell() { + string = NULL; + type = COLUMNTYPE_NUMERIC; + value = 0.0f; +} +hhDisplayCell::~hhDisplayCell() { + if (string) { + delete string; + string = NULL; + } +} +inline void hhDisplayCell::AllocateString() { + if (!string) { + string = new idStr; + } +} +inline void hhDisplayCell::operator=( const hhDisplayCell &cell ) { + if (cell.string) { + AllocateString(); + *string = *cell.string; + } + type = cell.type; + value = cell.value; +} +inline void hhDisplayCell::operator=(const idStr &text) { + AllocateString(); + *string = text; + type = COLUMNTYPE_ALPHA; +} +inline void hhDisplayCell::operator=(const char *text) { + AllocateString(); + *string = text; + type = COLUMNTYPE_ALPHA; +} +inline void hhDisplayCell::operator=(const int i) { + value = (float)i; + type = COLUMNTYPE_NUMERIC; +} +inline void hhDisplayCell::operator=(const float f) { + value = f; + type = COLUMNTYPE_NUMERIC; +} +inline hhDisplayCell & hhDisplayCell::operator+=(const int i) { + if (type == COLUMNTYPE_ALPHA) { + AllocateString(); + *this = atoi(String()) + i; + } + else { + value += (float)i; + } + type = COLUMNTYPE_NUMERIC; + return *this; +} +inline hhDisplayCell & hhDisplayCell::operator+=(const float f) { + if (type == COLUMNTYPE_ALPHA) { + AllocateString(); + *this = (float)atof(String()) + f; + } + else { + value += f; + } + type = COLUMNTYPE_NUMERIC; + return *this; +} +inline bool hhDisplayCell::IsInt() const { + return ((type == COLUMNTYPE_NUMERIC) && (value == (int)value) ); +} +inline int hhDisplayCell::StringLength() { + if (type == COLUMNTYPE_NUMERIC) { + return strlen(String()); + } + AllocateString(); + return string->Length(); +} +inline EColumnDataType hhDisplayCell::Type() const { + return type; +} +inline float hhDisplayCell::Float() const { + if (type == COLUMNTYPE_ALPHA) { + return (float)atof(String()); + } + return value; +} +inline int hhDisplayCell::Int() const { + if (type == COLUMNTYPE_ALPHA) { + return atoi(String()); + } + return (int)value; +} +const char *hhDisplayCell::String() const { + static idStr workStr; + if (type == COLUMNTYPE_NUMERIC) { + if (IsInt()) { + workStr = idStr((int)value); + } + else { + workStr = idStr(value); + } + return workStr.c_str(); + } + if (!string) { + return ""; + } + return string->c_str(); +} + +//========================================================================= +// +// hhDebugger +// +//========================================================================= + +hhDebugger::hhDebugger() { + bInitialized = false; + bClassCollapse = true; + bDrawEntities = false; + bPointer = false; + includeFilters = excludeFilters = 0; + filterClass = NULL; + potentialSet.Clear(); + displayList.Clear(); + selectedEntity = NULL; + sortColumn = 1; + sortAscending = false; + debugMode = DEBUGMODE_NONE; + gui=NULL; + displayColumnsUsed = 0; + + xaxis.Set(25, 0, 0); + yaxis.Set( 0, 25, 0); + zaxis.Set( 0, 0, 25); +} + +hhDebugger::~hhDebugger() { +} + +void hhDebugger::Shutdown() { + // Debugger must be shutdown manually because of the dict memeber which + // must be Cleared before the global string pool is emptied + dict.Clear(); + potentialSet.Clear(); + displayList.Clear(); + // release gui resources + bInitialized = false; +} + +void hhDebugger::Initialize() { + if (!gui) { + gui = uiManager->FindGui("guis/debugger.gui", true); + } + + SetMode(DEBUGMODE_STATS); + SetClassCollapse(true); + SetDrawEntities(false); + SetPointer(false); + ClearAllFilters(); + ExcludeFilter(FILTER_CLOSE); + ExcludeFilter(FILTER_DISTANT); + + bInitialized = true; +} + +void hhDebugger::Reset() { + selectedEntity = NULL; + filterClass = NULL; + Initialize(); +} + + +//========================================================================= +// +// hhDebugger::CaptureInput +// +//========================================================================= +void hhDebugger::CaptureInput(bool bCapture) { + if (bCapture && !bInteractive) { + // Just becoming interactive + const char *cmds = gui->Activate(true, gameLocal.time); + HandleGuiCommands(cmds); + } + else if (bInteractive && !bCapture) { + // Just becoming non-interactive + const char *cmds = gui->Activate(false, gameLocal.time); + HandleGuiCommands(cmds); + } + + bInteractive = bCapture; +} + +//========================================================================= +// +// hhDebugger::ColorForIndex +// +//========================================================================= +idVec4 hhDebugger::ColorForIndex(int index, float alpha) { + idVec4 color; + int bitmask = (index % 7) + 1; + color.x = (bitmask & 1) ? 1.0f : 0.0f; + color.y = (bitmask & 2) ? 1.0f : 0.0f; + color.z = (bitmask & 4) ? 1.0f : 0.0f; + color.w = alpha; + return color; +} + +//========================================================================= +// +// hhDebugger::ColorForEntity +// +//========================================================================= +idVec4 hhDebugger::ColorForEntity(idEntity *ent, float alpha) { + // Choose color based on editor_color if possible + idVec3 color3; + idVec4 color; + if (ent->spawnArgs.GetVector("editor_color", "0 0 1", color3)) { + color.x = color3.x; + color.y = color3.y; + color.z = color3.z; + color.w = alpha; + } + else { + color = ColorForIndex(ent->entityNumber, alpha); + } + return color; +} + +//========================================================================= +// +// hhDebugger::SetSortToColumn +// +//========================================================================= +void hhDebugger::SetSortToColumn(int col) { + if (hhDebugger::sortColumn == col) { + hhDebugger::sortAscending ^= 1; + } + else { + hhDebugger::sortColumn = col; + } +} + +//========================================================================= +// +// Filter routines +// +//========================================================================= +int hhDebugger::IndexForFilter(int filter) { //OBS + int index = hhMath::ILog2(filter); + return index; +} + +int hhDebugger::FilterForIndex(int index) { + return 1<SetStateInt(va("filter%i", filterIndex), FILTERSTATE_INCLUDED); + gui->HandleNamedEvent("SetFilter"); + } +} + +void hhDebugger::ExcludeFilter(int filterIndex) { + int filter = FilterForIndex(filterIndex); + excludeFilters |= filter; + includeFilters &= ~filter; + if (gui) { + gui->SetStateInt(va("filter%i", filterIndex), FILTERSTATE_EXCLUDED); + gui->HandleNamedEvent("SetFilter"); + } +} + +void hhDebugger::ClearFilter(int filterIndex) { + int filter = FilterForIndex(filterIndex); + includeFilters &= (~filter); + excludeFilters &= (~filter); + if (gui) { + gui->SetStateInt(va("filter%i", filterIndex), FILTERSTATE_NONE); + gui->HandleNamedEvent("SetFilter"); + } +} + +void hhDebugger::ClearAllFilters() { + for (int ix=0; ixDeleteStateVar( va( "listStats_item_%i", row ) ); + } + + gui->SetStateInt("mode", debugMode); + gui->HandleNamedEvent("SetMode"); + } +} + +void hhDebugger::SetClassCollapse(bool on) { + bClassCollapse = on; + if (gui) { + gui->SetStateBool("classcollapse", bClassCollapse); + gui->HandleNamedEvent("SetClassCollapse"); + } +} + +void hhDebugger::SetDrawEntities(bool on) { + bDrawEntities = on; + if (gui) { + gui->SetStateBool("drawEntities", bDrawEntities); + gui->HandleNamedEvent("SetDrawEntities"); + } +} + +void hhDebugger::SetPointer(bool on) { + bPointer = on; + if (gui) { + gui->SetStateBool("pointer", bPointer); + gui->HandleNamedEvent("SetPointer"); + } +} + +void hhDebugger::SetSelectedEntity(idEntity *ent) { + selectedEntity = ent; +} + + +//========================================================================= +// +// hhDebugger::HandleGuiCommands +// +//========================================================================= +bool hhDebugger::HandleGuiCommands(const char *commands) { + bool ret = false; + + if (commands && commands[0]) { + idLexer src; + src.LoadMemory( commands, strlen( commands ), "guiCommands" ); + + if (HandleSingleGuiCommand(gameLocal.GetLocalPlayer(), &src)) { + ret = true; + } + } + return ret; +} + +//========================================================================= +// +// hhDebugger::HandleSingleGuiCommand +// +//========================================================================= +bool hhDebugger::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { + idToken token; + + if (!src->ReadToken(&token)) { + return false; + } + else if (token == ";") { + return false; + } + else if (token.IcmpPrefix("guicmd_title_") == 0) { // "title_cNN" + token.ToLower(); + token.Strip("guicmd_title_c"); // "NN" + int col = atoi(token.c_str()); + SetSortToColumn(col); + return true; + } + else if (token.IcmpPrefix("guicmd_select") == 0) { // "guicmd_select" + int row = gui->State().GetInt("listStats_sel_0", "-1"); + if (row >= 0) { + TranslateRowCommand(row); + } + } + else if (token.IcmpPrefix("guicmd_cyclemode") == 0) { // "guicmd_cyclemode" + SetMode( (EDebugMode)((GetMode() + 1) % NUM_DEBUGMODES) ); + } + else if (token.IcmpPrefix("guicmd_drawentities") == 0) { // "guicmd_drawentities" + SetDrawEntities(!GetDrawEntities()); + } + else if (token.IcmpPrefix("guicmd_classcollapse") == 0) { // "guicmd_classcollapse" + SetClassCollapse(!GetClassCollapse()); + } + else if (token.IcmpPrefix("guicmd_pointer") == 0) { // "guicmd_pointer" + SetPointer(!GetPointer()); + } + else if (token.IcmpPrefix("guicmd_filter") == 0) { // "guicmd_filter" + token.ToLower(); + token.Strip("guicmd_filter"); // "NN" + int filterIndex = atoi(token.c_str()); + + // toggle 3-way state + Toggle3wayFilter(filterIndex); + + // If turning off the class filter, revert back to ClassCollapse + if (filterIndex == FILTER_CLASS && !FilterIncluded(FILTER_CLASS)) { + SetClassCollapse(true); + } + } + + src->UnreadToken(&token); + return false; +} + +//========================================================================= +// +// hhDebugger::TranslateRowCommand +// +//========================================================================= +void hhDebugger::TranslateRowCommand(int row) { + if (GetMode() != DEBUGMODE_STATS) { + return; + } + + // clicked on a row + if (GetClassCollapse()) { + idTypeInfo *clickedClass = ClassForRow(row); + if (clickedClass) { + IncludeFilter(FILTER_CLASS); + filterClass = clickedClass; + SetClassCollapse(false); + } + } + else { + idEntity *clickedEntity = EntityForRow(row); + if (clickedEntity) { + if (!selectedEntity.IsValid() || clickedEntity != selectedEntity.GetEntity()) { + selectedEntity = clickedEntity; + } + else { + if (GetMode()==DEBUGMODE_STATS) { + SetMode(DEBUGMODE_GAMEINFO1); + } + } + } + } +} + +//========================================================================= +// +// hhDebugger::DeterminePotentialSet +// +//========================================================================= +void hhDebugger::DeterminePotentialSet() { + + float maxDistSquared = g_maxShowDistance.GetFloat()*g_maxShowDistance.GetFloat(); + float minDistSquared = DEBUG_MIN_ENT_DIST*DEBUG_MIN_ENT_DIST; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 playerPosition = player->GetEyePosition(); + idEntity *ent; + + potentialSet.Clear(); +/*FIXME: Should use this format instead for speed +for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idLight::Type ) ) { + idLight *light = static_cast(ent); + } +}*/ + //FIXME: Use gameEdit->FindEntity ??? + for (int ix=0; ixGetType() != filterClass) ) { + continue; + } + if (FilterExcluded(FILTER_CLASS) && (ent->GetType() == filterClass) ) { + continue; + } + + if (FilterIncluded(FILTER_VISIBLE) && ent->IsHidden()) { // worthless, replace with more specific active filters (THINK,ANIM,UPDATEVISUALS,PHYSICS) + continue; + } + if (FilterExcluded(FILTER_VISIBLE) && !ent->IsHidden()) { + continue; + } + + if (FilterIncluded(FILTER_ACTIVE) && !ent->IsActive()) { + continue; + } + if (FilterExcluded(FILTER_ACTIVE) && ent->IsActive()) { + continue; + } + + if (FilterIncluded(FILTER_MONSTERS) && !ent->IsType(idAI::Type)) { + continue; + } + if (FilterExcluded(FILTER_MONSTERS) && ent->IsType(idAI::Type)) { + continue; + } + + if (FilterIncluded(FILTER_HASMODEL) && ent->GetModelDefHandle() == -1) { + continue; + } + if (FilterExcluded(FILTER_HASMODEL) && ent->GetModelDefHandle() != -1) { + continue; + } + + if (FilterIncluded(FILTER_EXPENSIVE) && ent->thinkMS < g_expensiveMS.GetFloat()) { + continue; + } + if (FilterExcluded(FILTER_EXPENSIVE) && ent->thinkMS > g_expensiveMS.GetFloat()) { + continue; + } + + if (FilterIsActive(FILTER_ANIMATING)) { + bool isAnimating = ent->GetAnimator() && ent->GetAnimator()->IsAnimating( gameLocal.time ); + if (FilterIncluded(FILTER_ANIMATING) && !isAnimating) { + continue; + } + if (FilterExcluded(FILTER_ANIMATING) && isAnimating) { + continue; + } + } + + if (FilterIsActive(FILTER_DORMANT)) { + + bool isDormant = ent->fl.isDormant; + if (FilterIncluded(FILTER_DORMANT) && !isDormant) { + continue; + } + if (FilterExcluded(FILTER_DORMANT) && isDormant) { + continue; + } + } + + if (FilterIsActive(FILTER_ACTIVESOUNDS)) { + bool playingSounds = ent->GetSoundEmitter() && ent->GetSoundEmitter()->CurrentlyPlaying(); + if (FilterIncluded(FILTER_ACTIVESOUNDS) && !playingSounds) { + continue; + } + if (FilterExcluded(FILTER_ACTIVESOUNDS) && playingSounds) { + continue; + } + } + + if (FilterIsActive(FILTER_CLOSE) || FilterIsActive(FILTER_DISTANT)) { + idVec3 entPos = ent->GetPhysics()->GetOrigin(); + idVec3 toEnt = entPos - playerPosition; + float distsqr = toEnt.LengthSqr(); + if (FilterIncluded(FILTER_CLOSE) && (distsqr > minDistSquared)) { + continue; + } + if (FilterExcluded(FILTER_CLOSE) && (distsqr < minDistSquared)) { + continue; + } + if (FilterIncluded(FILTER_DISTANT) && (distsqr < maxDistSquared)) { + continue; + } + if (FilterExcluded(FILTER_DISTANT) && (distsqr > maxDistSquared)) { + continue; + } + } + + potentialSet.Append(ent); + } +} + +//========================================================================= +// +// hhDebugger::DetermineSelectionSet +// +//========================================================================= +void hhDebugger::DetermineSelectionSet() { + + // Clear selectedEntity if it has been removed +// if (selectedEntity && !gameLocal.entities[selectedEntity->entityNumber]) { +// selectedEntity = NULL; +// } + + if (!GetPointer()) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 playerPosition = player->GetEyePosition(); + idVec4 color; + idVec3 entPos; + idVec3 toEnt; + idEntity *ent = NULL; + idEntity *bestEnt = NULL; + float bestScore = 0.0f; + + int num = potentialSet.Num(); + for (int ix=0; ixGetPhysics()->GetOrigin(); + toEnt = entPos - playerPosition; + float dist = toEnt.Length(); + float score = (toEnt * (player->viewAngles.ToMat3()[0])) / dist; + if (score > bestScore) { + bestScore = score; + bestEnt = ent; + } + } + + selectedEntity = bestEnt; +} + +//========================================================================= +// +// idListDefaultCompare +// +// Compares two pointers to hhDisplayItem. Used to sort. +//========================================================================= +template<> +ID_INLINE int idListSortCompare( const hhDisplayItem *a, const hhDisplayItem *b ) { + int sortColumn = hhDebugger::sortColumn; + + if (hhDebugger::sortAscending) { + if (a->column[sortColumn].Type() == COLUMNTYPE_ALPHA) { + return ( idStr::Icmp(b->column[sortColumn].String(), a->column[sortColumn].String() ) ); + } + else { // Sort numerically - need to multiply up because we're comparing integers + return (int)(1000.0 * ( a->column[sortColumn].Float() - + b->column[sortColumn].Float() )); // sort by percentage + } + } + else { + if (a->column[sortColumn].Type() == COLUMNTYPE_ALPHA) { + return ( idStr::Icmp(a->column[sortColumn].String(), b->column[sortColumn].String() ) ); + } + else { // Sort numerically - need to multiply up because we're comparing integers + return (int)(1000.0 * ( b->column[sortColumn].Float() - + a->column[sortColumn].Float() )); // sort by percentage + } + } +} + +const char *GetThinkFlags(idEntity *ent) { + static char buffer[20]; + + buffer[0] = 0; + if (ent->thinkFlags & TH_THINK) { + strcat(buffer, "T"); + } + if (ent->thinkFlags & TH_ANIMATE) { + strcat(buffer, "A"); + } + if (ent->thinkFlags & TH_PHYSICS) { + strcat(buffer, "P"); + } + if (ent->thinkFlags & TH_UPDATEVISUALS) { + strcat(buffer, "R"); + } + if (ent->thinkFlags & TH_UPDATEPARTICLES) { + strcat(buffer, "U"); + } + if (ent->thinkFlags & TH_TICKER) { + strcat(buffer, "K"); + } + + return buffer; +} + +//========================================================================= +// +// hhDebugger::FillDisplayList_Stats +// +//========================================================================= +void hhDebugger::FillDisplayList_Stats() { + idEntity *ent; + int slot, ix, num; + float totalThinkMS = 0.0f; + + num = potentialSet.Num(); + for (ix=0; ixGetClassname(); + slot = displayList.AddUnique(item); + } + else { + item.column[0] = ent->name; + slot = displayList.Append(item); // All entity names should be unique but just in case + } + + // Fill columns with appropriate stats + hhDisplayItem *row = &displayList[slot]; + if (GetClassCollapse()) { + //class count dormant (unused) active time % events + row->column[1] += 1; // Count - number in this class + if (ent->fl.isDormant) { // HUMANHEAD JRM - changed to fl.isDormant + row->column[2] += 1; // dormant + } + + // Column 3 is unused + row->column[3] = 0; + + if (ent->IsActive()) { + row->column[4] += 1; // active - number that are active + row->column[5] += ent->thinkMS; // thinkMS + totalThinkMS += ent->thinkMS; // column 6 is percentage + } + row->column[7] += idEvent::NumQueuedEvents(ent); + displayColumnsUsed = 8; + } + else { + //entity entitynum thFlags/active time % events + row->column[1] = ent->entityNumber; + if (ent->fl.isDormant) { // HUMANHEAD JRM - Changed to fl.isDormant + row->column[2] = 1; // dormant + } + + // Column 3 is unused + row->column[3] = 0; + + if (ent->IsActive()) { + row->column[4] = GetThinkFlags(ent); // active - number that are active + row->column[5] = ent->thinkMS; // thinkMS + totalThinkMS += ent->thinkMS; // column 6 is percentage + } + row->column[7] += idEvent::NumQueuedEvents(ent); + displayColumnsUsed = 8; + } + } + + // divide by totalMS to get percentage + num = displayList.Num(); + float recip = totalThinkMS > 0.0f ? (1.0f / totalThinkMS) : 0.0f; + for (ix=0; ixspawnArgs.GetNumKeyVals(); + for (int ix=0; ixspawnArgs.GetKeyVal(ix); + if (!kv->GetKey().IcmpPrefix("editor_")) { + continue; + } + + // Create a row with these two columns + hhDisplayItem item; + item.column[0] = kv->GetKey(); + item.column[1] = kv->GetValue(); + displayList.Append(item); + } + displayColumnsUsed = 2; + } +} + +//========================================================================= +// +// hhDebugger::FillDisplayList_GameInfo +// +//========================================================================= +void hhDebugger::FillDisplayList_GameInfo(int page) { + const idKeyValue *kv; + + if (selectedEntity.IsValid()) { + // Retrieve dictionary of variables from entity + dict.Clear(); + + selectedEntity->FillDebugVars(&dict, page); + + // Create a cleared item + hhDisplayItem item; + + // Fill item with dictionary entries + int num = dict.GetNumKeyVals(); + for (int ix=0; ixGetKey(); + item.column[1] = kv->GetValue(); + displayList.Append(item); + } + + displayColumnsUsed = 2; + } +} + +//========================================================================= +// +// hhDebugger::UpdateGUI_Entity +// +//========================================================================= +void hhDebugger::UpdateGUI_Entity() { + + // Fill display list depending on mode + displayList.Clear(); + displayColumnsUsed = 0; + switch(GetMode()) { + case DEBUGMODE_STATS: FillDisplayList_Stats(); break; + case DEBUGMODE_SPAWNARGS: FillDisplayList_SpawnArgs(); break; + case DEBUGMODE_GAMEINFO1: FillDisplayList_GameInfo(1); break; + case DEBUGMODE_GAMEINFO2: FillDisplayList_GameInfo(2); break; + case DEBUGMODE_GAMEINFO3: FillDisplayList_GameInfo(3); break; + case DEBUGMODE_NONE: break; + } + + // Sort display list based on sortColumn + displayList.Sort(); + + FillGUI(); + gui->StateChanged(gameLocal.time); // Notify the gui that variables have changed +} + +//========================================================================= +// +// hhDebugger::FillGUI +// +//========================================================================= +void hhDebugger::FillGUI() { + + int row,col; + int numrows = displayList.Num(); + int numcols = displayColumnsUsed; + char rowText[1024]; + + for (row=0; rowSetStateString( va("listStats_item_%i", row ), rowText ); + } + else { + gui->DeleteStateVar( va( "listStats_item_%i", row ) ); + } + } +} + +//========================================================================= +// +// hhDebugger::DrawPotentialSet +// +//========================================================================= +void hhDebugger::DrawPotentialSet() { + idVec4 color; + idVec3 entPos; + idEntity *ent; + + if (!GetDrawEntities()) { + return; + } + + // Draw potential entities as crosses + int num = potentialSet.Num(); + for (int ix=0; ixGetPhysics()) { + continue; + } + + // Draw cross at all entities and label + color = ColorForEntity(ent, 0.5f); + entPos = ent->GetPhysics()->GetOrigin(); + + hhUtils::DebugCross(color, entPos, 25); + gameRenderWorld->DrawText(ent->name.c_str(), entPos, 0.15f, color, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, 0); + } +} + +//========================================================================= +// +// hhDebugger::DrawSelectionSet +// +//========================================================================= +void hhDebugger::DrawSelectionSet() { + if (selectedEntity.IsValid() && selectedEntity->GetPhysics()) { + idVec3 entPos = selectedEntity->GetPhysics()->GetOrigin(); + idVec4 color = ColorForEntity(selectedEntity.GetEntity(), 1.0f); + + // Draw selected entities as highlighted + if (GetMode() != DEBUGMODE_GAMEINFO2) { + hhUtils::DebugCross(color*2, entPos, 10, 0); + } + + gameRenderWorld->DebugBox(color*2, + idBox(selectedEntity->GetPhysics()->GetBounds(), entPos, selectedEntity->GetPhysics()->GetAxis()), 0); + + // Allow entity to do custom debug drawing + switch(GetMode()) { + case DEBUGMODE_GAMEINFO1: + selectedEntity->DrawDebug(1); + break; + case DEBUGMODE_GAMEINFO2: + selectedEntity->DrawDebug(2); + break; + case DEBUGMODE_GAMEINFO3: + selectedEntity->DrawDebug(3); + break; + default: + selectedEntity->DrawDebug(0); + break; + } + } +} + +//========================================================================= +// +// hhDebugger::DrawGUI +// +//========================================================================= +void hhDebugger::DrawGUI() { + PROFILE_SCOPE("Profilers", PROFMASK_NORMAL); + if (gui) { + gui->Redraw(gameLocal.time); + } +} + +//========================================================================= +// +// hhDebugger::UpdateDebugger +// +//========================================================================= +void hhDebugger::UpdateDebugger() { + PROFILE_SCOPE("Profilers", PROFMASK_NORMAL); + + if (!gameLocal.GetLocalPlayer()) { + return; + } + + uiManager->SetDebuggerInteractive(IsInteractive()); + + // HUMANHEAD PCF pdm 05/14/06: Don't initialize/load the gui unless it's actually being used. + if (g_debugger.GetInteger() && !bInitialized) { + Initialize(); + } + + CaptureInput(gui && g_debugger.GetInteger() == 2); + + if (gui && g_debugger.GetInteger()) { + HandleFrameEvents(); + + DeterminePotentialSet(); + DetermineSelectionSet(); + + UpdateGUI_Entity(); + + DrawPotentialSet(); + DrawSelectionSet(); + + DrawGUI(); + + if (IsInteractive()) { + gui->DrawCursor(); + } + } +} + +// This code stolen from GuiFrameEvents() +void hhDebugger::HandleFrameEvents() { + const char *cmd; + sysEvent_t ev; + static int oldMouseX=0; + static int oldMouseY=0; + static int oldButton=0; + static bool oldDown=false; + int newX, newY; + int newButton; + bool buttonDown; + + memset( &ev, 0, sizeof( ev ) ); + + if (bInteractive) { + // fake up a mouse event based on the deltas tracked + // by the async usercmd generation + uiManager->GetMouseState( &newX, &newY, &newButton, &buttonDown ); + if ( newX != oldMouseX || newY != oldMouseY ) { + ev.evType = SE_MOUSE; + ev.evValue = newX - oldMouseX; + ev.evValue2 = newY - oldMouseY; + cmd = gui->HandleEvent( &ev, gameLocal.time ); + if ( cmd && cmd[0] ) { + HandleGuiCommands( cmd ); + } + + oldMouseX = newX; + oldMouseY = newY; + } + + if ( newButton != oldButton || buttonDown != oldDown) { + ev.evType = SE_KEY; + ev.evValue = newButton; + ev.evValue2 = buttonDown; + cmd = gui->HandleEvent( &ev, gameLocal.time ); + if ( cmd && cmd[0] ) { + HandleGuiCommands( cmd ); + } + oldButton = newButton; + oldDown = buttonDown; + } + } + + ev.evType = SE_NONE; + cmd = gui->HandleEvent( &ev, gameLocal.time ); + if ( cmd && cmd[0] ) { + gameLocal.Printf( "frame event returned: '%s'\n", cmd ); + } +} + +#endif //INGAME_DEBUGGER_ENABLED \ No newline at end of file diff --git a/neo/prey/Prey/sys_debugger.h b/neo/prey/Prey/sys_debugger.h new file mode 100644 index 00000000..336d5678 --- /dev/null +++ b/neo/prey/Prey/sys_debugger.h @@ -0,0 +1,187 @@ +#ifndef __SYS_DEBUGGER_H__ +#define __SYS_DEBUGGER_H__ + +#if INGAME_DEBUGGER_ENABLED + +#define DEBUG_MIN_ENT_DIST 75 // Minimum distance to entity for FILTER_DISTANCE +#define DEBUG_NUM_COLS 10 // Number of cols in debug_columns.gui (max supported) +#define DEBUG_NUM_ROWS 200 // Number of rows in debug_columns.gui (max supported) +#define NUM_DISPLAYLIST_COLUMNS 10 + +class idTypeInfo; + +enum EDebugMode { + DEBUGMODE_NONE=0, + DEBUGMODE_STATS, + DEBUGMODE_SPAWNARGS, + DEBUGMODE_GAMEINFO1, + DEBUGMODE_GAMEINFO2, + DEBUGMODE_GAMEINFO3, + NUM_DEBUGMODES +}; + +enum EColumnDataType { + COLUMNTYPE_ALPHA, + COLUMNTYPE_NUMERIC +}; + +// Filter bitmasks +#define FILTER_NONE 0 +#define FILTER_CLASS 1 +#define FILTER_ACTIVE 2 +#define FILTER_DISTANT 3 +#define FILTER_MONSTERS 4 +#define FILTER_ACTIVESOUNDS 5 +#define FILTER_HASMODEL 6 +#define FILTER_ANIMATING 7 +#define FILTER_EXPENSIVE 8 +#define FILTER_VISIBLE 9 +#define FILTER_CLOSE 10 +#define FILTER_DORMANT 11 +#define NUM_FILTERS 12 + +#define FILTERSTATE_NONE 0 +#define FILTERSTATE_INCLUDED 1 +#define FILTERSTATE_EXCLUDED 2 + +class hhDisplayCell { +public: + hhDisplayCell(); + ~hhDisplayCell(); + + inline void operator=( const idStr &text ); + inline void operator=( const char *text ); + inline void operator=( const int i ); + inline void operator=( const float f ); + inline void operator=( const hhDisplayCell &cell ); + + inline hhDisplayCell & operator+=( const int i ); + inline hhDisplayCell & operator+=( const float f ); + + inline bool IsInt() const; + inline int StringLength(); + inline EColumnDataType Type() const; + inline float Float() const; + inline int Int() const; + const char * String() const; + +private: + inline void AllocateString(); + + idStr *string; + EColumnDataType type; + float value; +}; + + + +class hhDisplayItem { +public: + hhDisplayCell column[NUM_DISPLAYLIST_COLUMNS]; + friend bool operator==( const hhDisplayItem &a, const hhDisplayItem &b ) { + return !idStr::Icmp(a.column[0].String(), b.column[0].String()); + }; + // idList<>::Append() uses operator= on it's objects, so we need to override the + // default memcpy() functionality, with something that constructs a new idStr in hhDisplayCell + // Force idList<> to copy each element individually, so hhDisplayCell::operator=() is used + inline void operator=( const hhDisplayItem &item ) { + for (int ix=0; ix selectedEntity; // Entity currently selected by GUI choice or pointer + + idVec3 xaxis; + idVec3 yaxis; + idVec3 zaxis; + + idTypeInfo *filterClass; // Class to be used when FILTER_CLASS is active + int includeFilters; // Bitmask of current inclusion filters used to cull potentialSet + int excludeFilters; // Bitmask of current exclusion filters used to cull potentialSet + + idList potentialSet; // All entities that pass the filters + idList displayList; + int displayColumnsUsed; // Number of columns of display list actually used + + EDebugMode debugMode; // Mode for debugger display + idUserInterface *gui; + idDict dict; // Scratch pad dictionary, needs to be persistent over + // the time of UpdateDebugger() +}; + + +extern hhDebugger debugger; + +#endif // INGAME_DEBUGGER_ENABLED + +#endif // __SYS_DEBUGGER_H__ diff --git a/neo/prey/Prey/sys_preycmds.cpp b/neo/prey/Prey/sys_preycmds.cpp new file mode 100644 index 00000000..c7c5e9a0 --- /dev/null +++ b/neo/prey/Prey/sys_preycmds.cpp @@ -0,0 +1,1123 @@ +// sys_preycmds.cpp +// + +#include "precompiled.h" +#pragma hdrstop + +#include "prey_local.h" + + +#if INGAME_PROFILER_ENABLED +void Profiler_ZoomOut_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TRAVERSEOUT); } +void Profiler_Mode_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLEMODE); } +void Profiler_MSMode_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLEMS); } +void Profiler_Smooth_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLESMOOTHING); } +void Profiler_ToggleCapture_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLECAPTURE); } +void Profiler_Toggle_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_TOGGLE); } +void Profiler_Release_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_RELEASE); } +void Profiler_In0_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN0); } +void Profiler_In1_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN1); } +void Profiler_In2_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN2); } +void Profiler_In3_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN3); } +void Profiler_In4_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN4); } +void Profiler_In5_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN5); } +void Profiler_In6_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN6); } +void Profiler_In7_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN7); } +void Profiler_In8_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN8); } +void Profiler_In9_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN9); } +void Profiler_In10_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN10); } +void Profiler_In11_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN11); } +void Profiler_In12_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN12); } +void Profiler_In13_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN13); } +void Profiler_In14_f( const idCmdArgs &args ) { profiler->SubmitFrameCommand(PROFCOMMAND_IN14); } +#endif + +void Cmd_GetMapName_f( const idCmdArgs &args ) { + gameLocal.Printf( "%s\n", gameLocal.GetMapName() ); +} + +void Cmd_TestText_f( const idCmdArgs &args ) { +#if 0 // jmarshall + char translated[1024]; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: testtext \n"); + return; + } + common->FixupKeyTranslations(args.Argv(1), translated, 1024); // No passing idStr between game and engine + gameLocal.Printf("%s\n", translated); +#endif +} + +void Cmd_GetBinds_f( const idCmdArgs &args ) { + const char *entityname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: getbinds \n"); + return; + } + + if ( !gameLocal.CheatsOk() ) { + return; + } + + // get name of entity + entityname = args.Argv( 1 ); + + // find entity by name or classname + idEntity *master = gameLocal.FindEntity(entityname); + if (master) { + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && ent->GetBindMaster() == master ) { + gameLocal.Printf(" %s bound (%s)\n", ent->GetName(), ent->GetClassname()); + count++; + } + } + gameLocal.Printf("%d entities bound to %s\n", count, entityname); + } +} + +void Cmd_SpiritWalkMode_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: envirosuit <1|0>\n"); + return; + } + + bool on = atoi(args.Argv( 1 )) != 0; + gameLocal.SpiritWalkSoundMode( on ); +} + +void Cmd_DialogMode_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: dialogmode <1|0>\n"); + return; + } + + bool on = atoi(args.Argv( 1 )) != 0; + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->IsType(hhPlayer::Type)) { + if (on) { + static_cast(player)->DialogStart(0, 1, 1); + } + else { + static_cast(player)->DialogStop(); + } + } +} + +void Cmd_EntitySize_f( const idCmdArgs &args ) { + //TEMP: For memory optimization + gameLocal.Printf("Sizeof(idDict): %d\n", (int)sizeof(idDict)); + gameLocal.Printf("Sizeof(idList): %d\n", 16); + gameLocal.Printf("Sizeof(idMat3): %d\n", (int)sizeof(idMat3)); + gameLocal.Printf("Sizeof(idVec3): %d\n", (int)sizeof(idVec3)); + + gameLocal.Printf("Sizeof(renderEntity_t): %d\n", (int)sizeof(renderEntity_t)); + gameLocal.Printf("Sizeof(refSound_t): %d\n", (int)sizeof(refSound_t)); + gameLocal.Printf("Sizeof(idAnimator): %d\n", (int)sizeof(idAnimator)); + + gameLocal.Printf("Sizeof(idClass): %d\n", (int)sizeof(idClass)); + gameLocal.Printf("sizeof(idEntity): %d\n", (int)sizeof(idEntity)); + gameLocal.Printf("Sizeof(idAI): %d\n", (int)sizeof(idAI)); + gameLocal.Printf("Sizeof(hhPlayer): %d\n", (int)sizeof(hhPlayer)); + + gameLocal.Printf("Number entities: %d\n", gameLocal.num_entities); + gameLocal.Printf("sizeof(idEntity): %d\n", (int)sizeof(idEntity)); + gameLocal.Printf("Total mem cost: %d\n", gameLocal.num_entities * (int)sizeof(idEntity)); +} + +void Cmd_Assert_f( const idCmdArgs &args ) { + assert(0); +} + +void Cmd_Dormant_f( const idCmdArgs &args ) { + const char *classname; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf("Usage: dormant \n"); + return; + } + + classname = args.Argv( 1 ); + const idTypeInfo *searchType = idClass::GetClass(classname); + + if (searchType) { + // make all entities of this class: neverdormant + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if (ent && ent->IsType(*searchType)) { + ent->fl.neverDormant = false; + count++; + } + } + gameLocal.Printf("%d %s entities unmarked neverdormant\n", count, classname); + } + else { + gameLocal.Printf("class not found, make sure capitalization is correct\n"); + } +} + +void Cmd_UnDormant_f( const idCmdArgs &args ) { + const char *classname; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf("Usage: undormant \n"); + return; + } + + classname = args.Argv( 1 ); + const idTypeInfo *searchType = idClass::GetClass(classname); + + if (searchType) { + // make all entities of this class: neverdormant + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + // if (ent && !idStr::Icmp(ent->GetClassname(), classname)) { + if (ent && ent->IsType(*searchType)) { + ent->fl.neverDormant = true; + count++; + } + } + + gameLocal.Printf("%d %s entities marked neverdormant\n", count, classname); + } + else { + gameLocal.Printf("class not found, make sure capitalization is correct\n"); + } +} + +void Cmd_TestHealthPulse_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->hud) { + player->healthPulse = true; + } +} +void Cmd_TestSpiritPulse_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if (player && player->hud) { + player->spiritPulse = true; + } +} + +void Cmd_SpawnABunch_f( const idCmdArgs &args ) { + const char *key, *value; + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + int i; + const char *classname; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() < 2 || !args.Argc() & 1 ) { // must always have an even number of arguments + gameLocal.Printf( "usage: spawn classname []\n" ); + return; + } + + int numberToSpawn = atoi(args.Argv(2)); + + classname = args.Argv( 1 ); + dict.Set( "classname", classname ); + + for (int ix=0; ixviewAngles.yaw + gameLocal.random.CRandomFloat()*10; + dict.Set( "angle", va( "%f", yaw + 180 ) ); + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 )*(10+gameLocal.random.RandomFloat()*50); + dict.Set( "origin", org.ToString() ); + + for( i = 3; i < args.Argc() - 1; i += 2 ) { + key = args.Argv( i ); + value = args.Argv( i + 1 ); + dict.Set( key, value ); + } + + //HACK + if (!idStr::Icmpn(classname, "movable_", 8)) { + idVec3 vel = idAngles(0,yaw,0).ToForward() * gameLocal.random.RandomFloat()*150; + dict.SetVector("init_velocity", vel); + } + + gameLocal.SpawnEntityDef( dict ); + } +} + +void Cmd_ListDictionary_f( const idCmdArgs &args ) { + const idDict *dict=NULL; + idEntity *ent=NULL; + const char *classname=NULL; + idStr key; + const idKeyValue *kv=NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: Dict [key]\n"); + return; + } + + // get classname + classname = args.Argv( 1 ); + dict = gameLocal.FindEntityDefDict( classname ); + if (!dict) { + gameLocal.Printf("Unknown entity definition: %s\n", classname); + return; + } + + // handle specific key + if (args.Argc()==3) { + key = args.Argv( 2 ); + + kv = dict->FindKey(key); + if (kv) { + gameLocal.Printf(" %30s %s\n", kv->GetKey().c_str(), kv->GetValue().c_str()); + } + } + else { + int num = dict->GetNumKeyVals(); + for (int ix=0; ixGetKeyVal(ix); + if (kv) { + gameLocal.Printf(" %30s %s\n", kv->GetKey().c_str(), kv->GetValue().c_str()); + } + } + } +} + +// id has a version of this as well, but ours is better +// Trigger all entities matching given name or class +void Cmd_HHTrigger_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + const char *classname; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: trigger \n" ); + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count=0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); + count++; + } + } + + // failed by name. try by entity number + if ( count == 0 ) { + int entityNumber = atoi(args.Argv( 1 )); + if ( entityNumber < gameLocal.num_entities ) { + idEntity *ent = gameLocal.entities[entityNumber]; + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); + } + } + } + + gameLocal.Printf( "Triggered %d entities\n", count ); +} + +// CJRPERSISTENTMERGE: id has a version of this as well, but ours is better +// Remove all of a specified class from the level +void Cmd_HHRemove_f( const idCmdArgs &args ) { + idPlayer *player; + const char *classname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: remove \n"); + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->PostEventMS( &EV_Remove, 0 ); + count++; + } + } + + gameLocal.Printf("%d %s entities removed\n", count, classname); +} + +// Hide all of a specified class or a specific name +void Cmd_Hide_f( const idCmdArgs &args ) { + idPlayer *player; + idEntity *ent = NULL; + const char *classname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: hide \n"); + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->Hide(); + count++; + } + } + + gameLocal.Printf("%d %s entities hidden\n", count, classname); +} + +// Show all of a specified class or a specific name +void Cmd_Show_f( const idCmdArgs &args ) { + idPlayer *player; + idEntity *ent = NULL; + const char *classname; + + if ( args.Argc() < 2 ) { + gameLocal.Printf("Usage: show \n"); + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // get name/class of entity + classname = args.Argv( 1 ); + + // find entity by name or classname + int count = 0; + for (int i = 0; i < gameLocal.num_entities; i++) { + idEntity *ent = gameLocal.entities[i]; + if ( ent && (!idStr::Icmp(ent->name, classname) || !idStr::Icmp(ent->GetClassname(), classname)) ) { + ent->Show(); + count++; + } + } + + gameLocal.Printf("%d %s entities shown\n", count, classname); +} + +/* +============ +Cmd_SpawnDebrisMass_f +============ +*/ +void Cmd_SpawnDebrisMass_f(const idCmdArgs &args) { + const char *temp; + + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "Usage: SpawnDebrisMass entityDef\n" ); + return; + } + + temp = args.Argv( 1 ); + + hhUtils::SpawnDebrisMass( temp, vec3_origin ); + + +} + +void Cmd_SetPlayerGravity_f( const idCmdArgs &args ) { + idVec3 gravDirection; + idStr temp; + hhPlayer *player; + int argIndex = 0; + + if ( args.Argc() < 3 ) { + gameLocal.Printf("Usage: SetPlayerGravity \n"); + return; + } + + player = static_cast( gameLocal.GetLocalPlayer() ); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + for( int ix = 0; ix < 3; ++ix ) { + temp = args.Argv( ++argIndex ); + if( temp.Icmp("-") == 0 ) { + temp = args.Argv( ++argIndex ); + gravDirection[ix] = -1 * atof( temp.c_str() ); + } else { + gravDirection[ix] = atof( temp.c_str() ); + } + } + + if( player->GetPhysics() ) { + player->OrientToGravity( true ); + player->GetPhysics()->SetGravity( gravDirection ); + } +} + + +//========================================================================= +// Cmd_ClosePortal_f +// +// Closes a portal if it intersects my current bounds (for testing) +//========================================================================= +void Cmd_ClosePortal_f(const idCmdArgs &args) { + if (!gameLocal.CheatsOk()) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + qhandle_t areaPortal = gameRenderWorld->FindPortal( player->GetPhysics()->GetAbsBounds() ); + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL ); + gameLocal.Printf("Portal closed\n"); + } +} + +//========================================================================= +// Cmd_OpenPortal_f +// +// Opens a portal if it intersects my current bounds (for testing) +//========================================================================= +void Cmd_OpenPortal_f(const idCmdArgs &args) { + if (!gameLocal.CheatsOk()) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + qhandle_t areaPortal = gameRenderWorld->FindPortal( player->GetPhysics()->GetAbsBounds() ); + if ( areaPortal ) { + gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE ); + gameLocal.Printf("Portal opened\n"); + } +} + +//========================================================================= +// +// Cmd_CallScriptFunc_f +// +//========================================================================= +void Cmd_CallScriptFunc_f( const idCmdArgs &args ) { + if( !gameLocal.CheatsOk() ) { + return; + } + + if( args.Argc() <= 1 ) { + gameLocal.Printf( "Usage: call